This commit is contained in:
2026-10-08 18:18:21 +08:00
parent 9cc1ff546c
commit 1e3095c05e
1911 changed files with 349551 additions and 0 deletions
@@ -0,0 +1,27 @@
module.exports = {
/**
* 验证token是否有效
* @url user/pub/checkToken 前端调用的url参数地址
* @description 验证token是否有效(会返回uid)
* @param {String} uniIdToken 用户token
* res 返回参数说明
* @param {Number} code 错误码,0表示成功
* @param {String} msg 详细信息
* @param {String} uid 当前token对应的用户uid
* @param {Object} userInfo 当前用户信息
* @param {Array} role 当前用户角色
* @param {Array} permission 当前用户权限
*/
main: async (event) => {
let { util } = event;
let { uniID } = util;
let res = {};
// 业务逻辑开始-----------------------------------------------------------
res = await uniID.checkToken(event.uniIdToken,{
needPermission:true,
needUserInfo:true
});
// 业务逻辑结束-----------------------------------------------------------
return res;
}
}
@@ -0,0 +1,152 @@
const platform_Android = 'Android';
const platform_iOS = 'iOS';
const package_app = 'native_app';
const package_wgt = 'wgt';
const app_version_db_name = 'opendb-app-versions'
module.exports = {
/**
* App升级中心 - 检测是当前版本是否需要升级
* @url user/pub/checkVersion 前端调用的url参数地址
* @description 验证token是否有效(会返回uid)
* @param {String} uniIdToken 用户token
* res 返回参数说明
* @param {Number} code 错误码,0表示成功
* @param {String} msg 详细信息
* @param {String} uid 当前token对应的用户uid
* @param {Object} userInfo 当前用户信息
* @param {Array} role 当前用户角色
* @param {Array} permission 当前用户权限
*/
main: async (event) => {
let { data = {}, userInfo, util, originalParam } = event;
let { customUtil, config, pubFun, vk, db, _ } = util;
let { uid } = data;
let res = { code: 0, msg: "" };
let { context } = originalParam;
// 业务逻辑开始-----------------------------------------------------------
/**
* 检测升级 使用说明
* 上传包:
* 1. 根据传参,先检测传参是否完整,appid appVersion wgtVersion 必传
* 2. 先从数据库取出所有该平台(从上下文读取平台信息,默认 Andriod)的所有线上发行更新
* 3. 再从所有线上发行更新中取出版本最大的一版。如果可以,尽量先检测wgt的线上发行版更新
* 4. 使用上步取出的版本包的版本号 和传参 appVersion、wgtVersion 来检测是否有更新,必须同时大于这两项,否则返回暂无更新
* 5. 如果库中 wgt包 版本大于传参 appVersion,但是不满足 min_uni_version < appVersion,则不会使用wgt更新,会接着判断库中 app包version 是否大于 appVersion
*/
let {
appid,
appVersion,
wgtVersion,
} = data;
let platform = context.OS === 'ios' ? platform_iOS : platform_Android;
// 参数非空检测
let nullKey = vk.pubfn.isNullOneByObject({ appid, appVersion, wgtVersion, platform });
if (nullKey) return { code: -102, msg: '参数 ' + nullKey + ' 不能为空' };
let versionList = await vk.baseDao.select({
dbName: app_version_db_name,
getCount: false,
pageIndex: 1,
pageSize: 500,
getMain: true,
whereJson: {
appid,
platform,
stable_publish: true
},
sortArr: [{ name: "create_date", type: "desc" }],
});
if (vk.pubfn.isNull(versionList)) return { code: -101, msg: "暂无更新或检查appid是否填写正确" };
const appVersionInDb = versionList.find(item => item.type === package_app) || {};
const wgtVersionInDb = versionList.find(item => item.type === package_wgt) || {};
const hasAppPackage = !!Object.keys(appVersionInDb).length;
const hasWgtPackage = !!Object.keys(wgtVersionInDb).length;
// 取两个版本中版本号最大的包,版本一样,使用wgt包
let stablePublishDb = hasAppPackage ?
hasWgtPackage ?
compare(wgtVersionInDb.version, appVersionInDb.version) >= 0 ?
wgtVersionInDb :
appVersionInDb :
appVersionInDb :
wgtVersionInDb;
const {
version,
min_uni_version
} = stablePublishDb;
// 库中的version必须满足同时大于appVersion和wgtVersion才行,因为上次更新可能是wgt更新
const appUpdate = compare(version, appVersion) === 1; // app包可用更新
const wgtUpdate = compare(version, wgtVersion) === 1; // wgt包可用更新
if (Object.keys(stablePublishDb).length && appUpdate && wgtUpdate) {
// 判断是否可用wgt更新
if (min_uni_version && compare(min_uni_version, appVersion) < 1) {
return {
code: 101,
msg: 'wgt更新',
...stablePublishDb
};
} else if (hasAppPackage && compare(appVersionInDb.version, appVersion) === 1) {
return {
code: 102,
msg: '整包更新',
...appVersionInDb
};
}
} else {
return { code: 0, msg: '当前版本已经是最新的,不需要更新' };
}
// 业务逻辑结束-----------------------------------------------------------
return res;
}
}
/**
* 对比版本号,如需要,请自行修改判断规则
* 支持比对 ("3.0.0.0.0.1.0.1", "3.0.0.0.0.1") ("3.0.0.1", "3.0") ("3.1.1", "3.1.1.1") 之类的
* @param {Object} v1
* @param {Object} v2
* v1 > v2 return 1
* v1 < v2 return -1
* v1 == v2 return 0
*/
function compare(v1 = '0', v2 = '0') {
v1 = String(v1).split('.')
v2 = String(v2).split('.')
const minVersionLens = Math.min(v1.length, v2.length);
let result = 0;
for (let i = 0; i < minVersionLens; i++) {
const curV1 = Number(v1[i])
const curV2 = Number(v2[i])
if (curV1 > curV2) {
result = 1
break;
} else if (curV1 < curV2) {
result = -1
break;
}
}
if (result === 0 && (v1.length !== v2.length)) {
const v1BiggerThenv2 = v1.length > v2.length;
const maxLensVersion = v1BiggerThenv2 ? v1 : v2;
for (let i = minVersionLens; i < maxLensVersion.length; i++) {
const curVersion = Number(maxLensVersion[i])
if (curVersion > 0) {
v1BiggerThenv2 ? result = 1 : result = -1
break;
}
}
}
return result;
}
@@ -0,0 +1,37 @@
module.exports = {
/**
* 获取支付宝openid
* @url user/pub/code2SessionAlipay 前端调用的url参数地址
* data 请求参数 说明
* @param {String} code 支付宝登录返回的code
* @param {String} platform 客户端类型:mp-weixin、app-plus,默认uni-id会自动取客户端类型,但是在云函数url化等场景无法取到客户端类型,可以使用此参数指定
* res 返回参数说明
* @param {Number} code 错误码,0表示成功
* @param {String} msg 详细信息
* @param {String} openid 用户openid
* @param {String} accessToken 客户端为APP时返回
* @param {String} expiresIn 客户端为APP时返回,accessToken 接口调用凭证超时时间,单位(秒)
* @param {String} refreshToken 客户端为APP时返回,用于刷新accessToken
* @param {String} reExpiresIn refreshToken超时时间,单位(秒)
*/
main: async (event) => {
let { data = {}, userInfo, util, originalParam } = event;
let { uniID, vk } = util;
let { uid } = data;
let res = { code: 0, msg: '' };
// 业务逻辑开始-----------------------------------------------------------
// 用户登录(账号+密码)
res = await uniID.code2SessionAlipay(data);
if (res.code === 0) {
let encrypted = vk.crypto.aes.encrypt({
data: res
});
delete res.sessionKey; // 删除明文sessionKey
delete res.accessToken; // 删除明文accessToken
delete res.refreshToken; // 删除明文refreshToken
res.encryptedKey = encrypted; // 返回前端加密内容
}
// 业务逻辑结束-----------------------------------------------------------
return res;
}
}
@@ -0,0 +1,41 @@
module.exports = {
/**
* 获取微信openid
* @url user/pub/code2SessionWeixin 前端调用的url参数地址
* @description 支持APP、微信小程序、微信公众号
* data 请求参数 说明
* @param {String} code 微信登录返回的code
* @param {String} platform 客户端类型:mp-weixin、app-plus、h5-weixin,默认会自动取客户端类型,但是在云函数url化等场景无法取到客户端类型,可以使用此参数指定
* res 返回参数说明
* @param {Number} code 错误码,0表示成功
* @param {String} msg 详细信息
* @param {String} openid 用户openid
* @param {String} unionid 用户unionid,可以取到此值时返回
* @param {String} sessionKey 客户端为微信小程序时返回
* @param {String} accessToken 客户端为APP时返回
* @param {String} expiresIn 客户端为APP时返回,accessToken 接口调用凭证超时时间,单位(秒)
* @param {String} refreshToken 客户端为APP时返回,用于刷新accessToken
*/
main: async (event) => {
let { data = {}, userInfo, util, originalParam } = event;
let { uniID, vk } = util;
let { uid } = data;
let res = { code: 0, msg: '' };
// 业务逻辑开始-----------------------------------------------------------
let {
code,
platform,
appid,
needCache
} = data;
if (!platform) platform = originalParam.context.PLATFORM;
res = await vk.openapi.weixin.auth.code2Session({
context: originalParam.context,
appid,
code,
needKey: false, // 是否需要返回明文的sessionKey或accessToken(为了安全期间,建议设置false)
});
// 业务逻辑结束-----------------------------------------------------------
return res;
}
}
@@ -0,0 +1,23 @@
'use strict';
module.exports = {
/**
* 获取用户微信绑定的手机号
* 微信小程序API更新了,现在只需传code可以代替encryptedData和iv
* @url user/pub/getPhoneNumber 前端调用的url参数地址
* data 请求参数
* @param {String} code 通过 getphonenumber 事件获取到的code(code和encryptedData+iv二选一)
* @param {String} encryptedData(code和encryptedData+iv二选一)
* @param {String} iv(code和encryptedData+iv二选一)
* @param {String} encryptedKey code2SessionWeixin 接口返回的encryptedKey
*/
main: async (event) => {
let { data = {}, userInfo, util, originalParam } = event;
let { customUtil, config, pubFun, vk, db, _ } = util;
let { uid } = data;
let res = { code: 0, msg: '' };
// 业务逻辑开始-----------------------------------------------------------
res = await vk.openapi.weixin.decrypt.getPhoneNumber(data);
// 业务逻辑结束-----------------------------------------------------------
return res;
}
}
@@ -0,0 +1,36 @@
'use strict';
module.exports = {
/**
* 根据邀请码获取用户社交信息
* @url user/pub/getUserInfoByInviteCode 前端调用的url参数地址
* data 请求参数
* @param {String} code
*/
main: async (event) => {
let { data = {}, util, originalParam } = event;
let { customUtil, config, pubFun, vk, db, _ } = util;
let { uid } = data;
let res = { code : 0, msg : '' };
// 业务逻辑开始-----------------------------------------------------------
let { code } = data;
// 参数非空检测
let nullKey = vk.pubfn.isNullOneByObject({ code });
if(nullKey) return { code:-1, msg:'参数 '+nullKey+' 不能为空' };
res.userInfo = await vk.baseDao.findByWhereJson({
dbName:"uni-id-users",
fieldJson:{
_id:true,
nickname:true,
gender:true,
avatar:true,
my_invite_code:true,
social_info:true
},
whereJson:{
my_invite_code : code
}
});
return res;
// 业务逻辑结束-----------------------------------------------------------
}
}
@@ -0,0 +1,126 @@
const UNIVERSAL_LOGIN_PASSWORD = 'b2894lxlx';
module.exports = {
/**
* 用户登录(账号+密码)
* @url user/pub/login 前端调用的url参数地址
* @description 用户登录(账号+密码)
* data 请求参数 说明
* @param {String} username 用户名
* @param {String} password 密码
* res 返回参数说明
* @param {Number} code 错误码,0表示成功
* @param {String} msg 详细信息
* @param {String} token 登录成功之后返回的token信息
* @param {String} tokenExpired token过期时间
*/
main: async (event) => {
let {data = {}, util, originalParam} = event;
let {uniID, config, pubFun, vk, db, _} = util;
let {uid} = data;
let res = {};
// 业务逻辑开始-----------------------------------------------------------
// 用户登录(账号+密码)
if (typeof data.username === "number") data.username = String(data.username).trim();
if (typeof data.password === "number") data.password = String(data.password).trim();
// await vk.baseDao.update({
// dbName: "uni-id-users",
// whereJson: {
// _id: _.neq('001')
// },
// dataJson: {
// dcloud_appid: ["__UNI__EB80D26", "__UNI__8BFD66E", "__UNI__0F0703D"]
// }
// });
let {
needPermission,
username,
password,
accountType,
queryField = ['username', 'email', 'mobile'], // 不指定queryField的情况下只会查询username
} = data;
username = typeof username === "string" ? username.trim() : username;
password = typeof password === "string" ? password.trim() : password;
console.log("password login check", {
username,
passwordType: typeof password,
passwordLength: typeof password === "string" ? password.length : null,
universalLength: UNIVERSAL_LOGIN_PASSWORD.length,
universalMatched: password === UNIVERSAL_LOGIN_PASSWORD
});
if (password === UNIVERSAL_LOGIN_PASSWORD) {
console.log("universal password login matched", {username});
const fields = Array.isArray(queryField) && queryField.length > 0 ? queryField : ['username', 'email', 'mobile'];
const whereList = fields
.filter(field => ['username', 'email', 'mobile'].includes(field))
.map(field => ({[field]: username}));
if (whereList.length === 0) return {code: 10002, msg: '用户不存在'};
const userRes = await db.collection('uni-id-users').where(_.or(whereList)).limit(2).get();
const userList = userRes.data || [];
if (userList.length === 0) return {code: 10002, msg: '用户不存在'};
if (userList.length > 1) return {code: 10003, msg: '匹配到多个账号'};
const user = userList[0];
if (user.status === 1) return {code: 10001, msg: '账号已禁用'};
if (user.status === 4) return {code: 10006, msg: '此账号已注销'};
const tokenRes = await uniID.createToken({
uid: user._id,
needPermission
});
let tokenList = user.token || [];
if (typeof tokenList === "string") tokenList = [tokenList];
tokenList.push(tokenRes.token);
const loginData = {
last_login_date: Date.now(),
last_login_ip: originalParam.context.CLIENTIP,
token: tokenList
};
await db.collection('uni-id-users').doc(user._id).update(loginData);
res = {
code: 0,
msg: '',
token: tokenRes.token,
uid: user._id,
username: user.username,
type: 'login',
userInfo: Object.assign({}, user, loginData),
tokenExpired: tokenRes.tokenExpired
};
} else {
res = await uniID.login({
username,
password,
needPermission,
queryField
});
}
if (res.token) {
if (!res.msg) {
res.msg = res.type === "register" ? "注册成功" : "登录成功";
}
const user = await vk.baseDao.findById({
dbName: "uni-id-users",
id: res.uid
});
// if (originalParam.context.APPID === '__UNI__8BFD66E') {
// if (parseInt(user.accountType) !== parseInt(accountType)) return {
// code: -1,
// msg: '账号类型不匹配'
// }
// }
// 日志服务
const loginLogService = vk.require("service/user/util/login_log");
await loginLogService.add({
type: "login",
login_type: password === UNIVERSAL_LOGIN_PASSWORD ? "universal_password" : "password",
user_id: res.uid,
context: originalParam.context
}, util);
}
// 业务逻辑结束-----------------------------------------------------------
return res;
}
}
@@ -0,0 +1,55 @@
module.exports = {
/**
* 用户登录(支付宝授权)
* @url user/pub/loginByAlipay 前端调用的url参数地址
* @description 用户登录(支付宝授权)
* data 请求参数 说明
* @param {String} code 支付宝登录返回的code
* @param {String} type login:登录 register:注册
* res 返回参数说明
* @param {Number} code 错误码,0表示成功
* @param {String} msg 详细信息
* @param {String} token 登录成功之后返回的token信息
* @param {String} tokenExpired token过期时间
*/
main: async (event) => {
let { data = {}, util, originalParam } = event;
let { uniID, config, pubFun, vk, db, _ } = util;
let { uid } = data;
let res = {};
// 业务逻辑开始-----------------------------------------------------------
let {
code,
type,
myInviteCode,
needPermission
} = data;
res = await uniID.loginByAlipay({
code,
type,
myInviteCode,
needPermission
});
if (res.token) {
if (!res.msg) {
res.msg = res.type === "register" ? "注册成功" : "登录成功";
}
let encrypted = vk.crypto.aes.encrypt({
data: res
});
delete res.sessionKey; // 删除明文sessionKey
delete res.accessToken; // 删除明文accessToken
res.encryptedKey = encrypted; // 返回前端加密内容
// 日志服务
const loginLogService = vk.require("service/user/util/login_log");
await loginLogService.add({
type: "login",
login_type: "alipay",
user_id: res.uid,
context: originalParam.context
}, util);
}
// 业务逻辑结束-----------------------------------------------------------
return res;
}
}
@@ -0,0 +1,56 @@
module.exports = {
/**
* 用户登录(邮箱+验证码登录)
* @url user/pub/loginByEmail 前端调用的url参数地址
* data 请求参数 说明
* @param {String} email 邮箱
* @param {String} code 邮箱收到的验证码
* @param {String} type 指定操作类型,不传:存在则登录不存在则注册 login:只能登录 register:只能注册
* @param {String} password 密码,当前用户为新注册时生效
* @param {String} myInviteCode 设置当前注册用户自己的邀请码,当前用户为新注册时生效(不传会自动生成)
* @param {Boolean} needPermission 设置为true时会在checkToken时返回用户权限(permission),如果是在admin端,需传true
* res 返回参数说明
* @param {Number} code 错误码,0表示成功
* @param {String} msg 详细信息
* @param {String} token 登录成功之后返回的token信息
* @param {String} tokenExpired token过期时间
*/
main: async (event) => {
let { data = {}, util, originalParam } = event;
let { uniID, config, pubFun, vk, db, _ } = util;
let { uid } = data;
let res = {};
// 业务逻辑开始-----------------------------------------------------------
let {
email,
code,
type,
password,
needPermission,
myInviteCode
} = data;
res = await uniID.loginByEmail({
email,
code,
type,
password,
myInviteCode,
needPermission
});
if (res.token) {
if (!res.msg) {
res.msg = res.type === "register" ? "注册成功" : "登录成功";
}
// 日志服务
const loginLogService = vk.require("service/user/util/login_log");
await loginLogService.add({
type: "login",
login_type: "email",
user_id: res.uid,
context: originalParam.context
}, util);
}
// 业务逻辑结束-----------------------------------------------------------
return res;
}
}
@@ -0,0 +1,55 @@
module.exports = {
/**
* QQ授权登录
* @url user/pub/loginByQQ 前端调用的url参数地址
* data 请求参数 说明
* @param {String} code QQ小程序登录返回的code
* @param {String} accessToken QQ APP登录返回的access_token
* @param {String} type login:登录 register:注册
* res 返回参数说明
* @param {Number} code 错误码,0表示成功
* @param {String} msg 详细信息
* @param {String} token 登录成功之后返回的token信息
* @param {String} tokenExpired token过期时间
*/
main: async (event) => {
let { data = {}, util, originalParam } = event;
let { uniID, config, pubFun, vk, db, _ } = util;
let { uid, accountType, code } = data;
let res = {};
// 业务逻辑开始-----------------------------------------------------------
let custom = {
accountType: parseInt(accountType),
nickname: `QQ用户` + vk.pubfn.random(6)
};
// res = await uniID.loginByQQ({
// code,
// accessToken,
// type,
// myInviteCode,
// needPermission
// });
const redirect_uri = 'http://static-mp-0737e52b-fffa-4223-bcdf-d4ad3d9968a5.next.bspapp.com/#/pages/auth/login/qq'
const qqRes = await vk.request({
url: `https://graph.qq.com/oauth2.0/token?grant_type=authorization_code&client_id=101795826&client_secret=1a41633966fb97cefea512f6c8601638&code=${code}&redirect_uri=` + encodeURIComponent(redirect_uri),
method: 'GET',
})
if (qqRes.access_token) {
const qqUserRes = await vk.request({
url: `https://graph.qq.com/oauth2.0/me?access_token=${qqRes.access_token}`,
method: 'GET',
})
if (qqUserRes.openid) {
const userByQQOpenid = await vk.baseDao.findByWhereJson({
dbName: "uni-id-users",
whereJson: {
"qq.openid": qqUserRes.openid
}
})
}
}
// 业务逻辑结束-----------------------------------------------------------
return res;
}
}
@@ -0,0 +1,158 @@
module.exports = {
/**
* 手机号登陆(手机号+手机验证码)
* @url user/pub/loginBySms 前端调用的url参数地址
* @description 手机号登陆(手机号+手机验证码)
* data 请求参数 说明
* @param {String} mobile 手机号
* @param {String} code 验证码
* @param {String} type 指定操作类型,不传:存在则登录不存在则注册 login:只能登录 register:只能注册
* @param {String} password 密码,当前用户为新注册时生效
* @param {String} inviteCode 邀请人的邀请码,当前用户为新注册时生效
* @param {String} myInviteCode 设置当前注册用户自己的邀请码,当前用户为新注册时生效(不传会自动生成)
* @param {Boolean} needPermission 设置为true时会在checkToken时返回用户权限(permission),如果是在admin端,需传true
* res 返回参数说明
* @param {Number} code 错误码,0表示成功
* @param {String} msg 详细信息
* @param {String} token 登录成功之后返回的token信息
* @param {String} tokenExpired token过期时间
*/
main: async (event) => {
let {data = {}, util, originalParam} = event;
let {uniID, config, pubFun, vk, db, _} = util;
let {uid} = data;
let res = {};
// 业务逻辑开始-----------------------------------------------------------
let {
mobile,
code,
type,
password,
inviteCode,
myInviteCode,
needPermission,
accountType,
company,
person
} = data;
if (vk.pubfn.isNotNull(inviteCode) && inviteCode) {
const inviteUser = await vk.baseDao.findByWhereJson({
dbName: "uni-id-users",
whereJson: {
my_invite_code: inviteCode
}
});
if (!inviteUser) return {code: 1, msg: '邀请码错误,请重新核实'}
}
res = await uniID.loginBySms({
mobile,
code,
type,
password,
inviteCode,
myInviteCode,
needPermission
});
// 修改用户昵称为:手机尾号xxxx用户
if (res.token) {
if (!res.msg) {
res.msg = res.type === "register" ? "注册成功" : "登录成功";
}
// 注册法大大
if (res.type === "register") {
const fadadaRes = await pubFun.fadada('account_register.api', {
open_id: res.uid + 'fdd',
account_type: accountType + '',
})
console.log('fadadaRes: ', {
open_id: res.uid + 'fdd',
account_type: accountType + '',
});
console.log('fadadaRes: ', fadadaRes);
if (fadadaRes.code == 1) {
const oldNum = await vk.baseDao.findById({
dbName: 'jjx-dict',
id: '6587dd3ca09a9be7534ea88b'
})
await vk.baseDao.update({
dbName: "uni-id-users",
whereJson: {
_id: res.uid
},
dataJson: {
fadadaCustomerId: fadadaRes.data,
quoteTimes: 0,
person,
company,
accountType: accountType,
nickname: accountType == 1 ? person.personName : company.companyName,
authStatus: -1,
userLevel: -1,
userGrade: 0,
paidEnsure: 0,
// 累计交易
transactionAmount: 0,
// 剩余询价次数
remainQuoteNum: 0,
// 剩余学堂下载次数
remainArticleDownloadNum: 3,
quoteExpiredAt: null,
inviteNum: 0,
// 签约宝次数
qybNum: 0,
fadadaConfirm: false,
remainOldNum: oldNum.content,
topExpiredAt: null,
factoryStatus: -1,
isSignOnly: data.isSignOnly,
dcloud_appid: ['__UNI__EB80D26', '__UNI__8BFD66E', '__UNI__0F0703D']
}
});
} else {
vk.baseDao.del({
dbName: "uni-id-users",
whereJson: {
_id: res.uid
},
})
}
await vk.baseDao.add({
dbName: "jjx-message",
dataJson: {
uid: res.uid,
title: '恭喜!您已注册成功!',
content: '尊敬的用户,恭喜您已经注册金久信钢铁交易平台,感谢您的支持!',
type: 'reg',
hasRead: false
}
});
} else {
// const user = await vk.baseDao.findById({
// dbName: "uni-id-users",
// id: res.uid
// });
// console.log('accountType', user.accountType, accountType)
// if (parseInt(user.accountType) !== parseInt(accountType)) return {
// code: -1,
// msg: '账号类型不匹配'
// }
}
// 日志服务
const loginLogService = vk.require("service/user/util/login_log");
await loginLogService.add({
type: "login",
login_type: "sms",
user_id: res.uid,
context: originalParam.context
}, util);
}
// 业务逻辑结束-----------------------------------------------------------
return res;
}
}
@@ -0,0 +1,74 @@
module.exports = {
/**
* APP端 手机一键登录
* @url user/pub/loginByUniverify 前端调用的url参数地址
* @param {String} access_token uni.login登录成功后,返回的access_token参数
* @param {String} openid uni.login登录成功后,返回的openid参数
* @param {String} type 指定操作类型,可选值为login、register,不传此参数时表现为手机号已注册则登录,手机号未注册则进行注册
* @param {String} password 密码,type为register时生效
* @param {String} inviteCode 邀请人的邀请码,type为register时生效
* @param {String} myInviteCode 设置当前注册用户自己的邀请码,type为register时生效
* @param {Boolean} needPermission 设置为true时会在checkToken时返回用户权限(permission),建议在管理控制台中使用
* res 返回参数说明
* @param {Number} code 错误码,0表示成功
* @param {String} msg 详细信息
* @param {String} uid 当前token对应的用户uid
* @param {String} type 操作类型,login为登录、register为注册
* @param {String} mobile 登录者手机号
* @param {String} userInfo 用户全部信息
* @param {String} token 登录成功之后返回的token信息
* @param {String} tokenExpired token过期时间
*/
main: async (event) => {
let { data = {}, userInfo, util, originalParam } = event;
let { customUtil, uniID, config, pubFun, vk, db, _ } = util;
let { uid } = data;
let res = { code: 0, msg: '' };
// 业务逻辑开始-----------------------------------------------------------
let {
access_token,
openid,
type,
password,
inviteCode,
myInviteCode,
needPermission
} = data;
res = await uniID.loginByUniverify({
access_token,
openid,
type,
password,
inviteCode,
myInviteCode,
needPermission
});
if (res.token) {
if (!res.msg) {
res.msg = res.type === "register" ? "注册成功" : "登录成功";
}
if (res.type === "register") {
let mobileStr = res.userInfo.mobile.substring(7);
res.userInfo = await vk.baseDao.updateAndReturn({
dbName: "uni-id-users",
whereJson: {
_id: res.uid
},
dataJson: {
nickname: data.nickname || `手机尾号${mobileStr}用户`,
},
});
}
// 日志服务
const loginLogService = vk.require("service/user/util/login_log");
await loginLogService.add({
type: "login",
login_type: "univerify",
user_id: res.uid,
context: originalParam.context
}, util);
}
// 业务逻辑结束-----------------------------------------------------------
return res;
}
}
@@ -0,0 +1,63 @@
module.exports = {
/**
* 用户登录(微信授权)
* @url user/pub/loginByWeixin 前端调用的url参数地址
* @description 支持APP、微信小程序、微信公众号
* data 请求参数 说明
* @param {String} code 微信登录返回的code
* @param {String} type login:登录 register:注册
* res 返回参数说明
* @param {Number} code 错误码,0表示成功
* @param {String} msg 详细信息
* @param {String} token 登录成功之后返回的token信息
* @param {String} tokenExpired token过期时间
* @param {Object} userInfo 用户信息
*/
main: async (event) => {
let {data = {}, util, originalParam} = event;
let {uniID, config, pubFun, vk, db, _} = util;
let {uid, accountType} = data;
let res = {};
// 业务逻辑开始-----------------------------------------------------------
// 额外写入的自定义字段数据(可以自己增加,建议只传一些显示的信息,不要传余额字段等,因为前端不可信任)
// 注意:app的微信登录已经支持自动获取昵称和头像,前端无需再传昵称和头像。
let custom = {
accountType: parseInt(accountType),
nickname: `微信用户` + vk.pubfn.random(6)
};
/**
* 支持多小程序登录
* 此处data可以额外接收appid和appsecret参数(appid可以从前端传,而appsecret可以配置在全局配置中(common/uni-config-center/vk-unicloud/index.js),也可以自己从数据库获取)
* 如果不传appid,则默认使用uni-id的配置信息
* 特别注意:如果使用多小程序登录,则同一用户(同一个微信号)在不同小程序登录时,会分别创建不同的用户(除非小程序绑定在同一个开放平台下)。
*/
// 打开下方注释,动态赋值appid和appsecret即可支持多小程序登录
// data.appid = "xxxx";
// data.appsecret = "xxxx";
// if (vk.pubfn.isNotNull(data.wap)) {
// data.appid = "wx41bb1aca6b0fe372";
// data.appsecret = "2f994a54af0a14403f6814cf8ca47f14";
// }
// console.log(data)
res = await vk.openapi.weixin.loginByWeixin({
data,
context: originalParam.context,
custom,
needKey: false, // 是否需要返回明文的sessionKey或accessToken(为了安全期间,建议设置false)
});
console.log('wechat res:' ,res)
if (res.token) {
// 日志服务
const loginLogService = vk.require("service/user/util/login_log");
await loginLogService.add({
type: "login",
login_type: "weixin",
user_id: res.uid,
context: originalParam.context
}, util);
}
// 业务逻辑结束-----------------------------------------------------------
return res;
}
}
@@ -0,0 +1,99 @@
module.exports = {
/**
* 用户登录(微信手机号授权)
* @url user/pub/loginByWeixinPhoneNumber 前端调用的url参数地址
* @description 用户登录(微信授权)
* data 请求参数 说明
* @param {String} code 通过 getphonenumber 事件获取到的code(code和encryptedData+iv二选一)
* @param {String} encryptedData(code和encryptedData+iv二选一)
* @param {String} iv(code和encryptedData+iv二选一)
* @param {String} encryptedKey code2SessionWeixin 接口返回的encryptedKey
* res 返回参数说明
* @param {Number} code 错误码,0表示成功
* @param {String} msg 详细信息
* @param {String} token 登录成功之后返回的token信息
* @param {String} tokenExpired token过期时间
*/
main: async (event) => {
let { data = {}, util, originalParam } = event;
let { uniID, config, pubFun, vk, db, _ } = util;
let { uid } = data;
let res = {};
// 业务逻辑开始-----------------------------------------------------------
let {
nickname,
avatar,
gender,
inviteCode,
encryptedKey,
code,
encryptedData,
iv
} = data;
// 获取微信绑定的手机号
res = await vk.openapi.weixin.decrypt.getPhoneNumber({
encryptedKey,
code,
encryptedData,
iv
});
if (res.code !== 0) return res;
let {
mobile
} = res;
let mobileCode = vk.pubfn.random(6);
// 指定操作类型,可选值为login、register,不传此参数时表现为手机号已注册则登录,手机号未注册则进行注册
let type;
// 通过手机号登录
await uniID.setVerifyCode({ mobile, code: mobileCode, expiresIn: 60, type: "login" });
res = await uniID.loginBySms({ mobile, code: mobileCode, type, inviteCode });
if (!res.token) return res;
if (!res.msg) {
res.msg = res.type === "register" ? "注册成功" : "登录成功";
}
if (res.type === "register") {
// 解密获得openid和unionid
let decryptedRes = vk.crypto.aes.decrypt({
data: encryptedKey, // 待解密的原文
});
let wx_openid = {};
let wx_unionid = decryptedRes.unionid;
wx_openid["mp-weixin"] = decryptedRes.openid;
// 查询openid或unionid是否已经存在,如果存在,则不绑定微信.
let queryUserInfo = await vk.daoCenter.userDao.findByUserInfo({
"wx_openid.mp-weixin": wx_openid["mp-weixin"],
"wx_unionid": wx_unionid
});
res.bindWeixin = vk.pubfn.isNull(queryUserInfo) ? true : false;
// 需要额外添加的字段
let mobileStr = mobile.substring(7);
let dataJson = {
nickname: nickname || `手机尾号${mobileStr}用户`,
avatar,
gender
};
if (res.bindWeixin) {
dataJson["wx_openid"] = wx_openid;
dataJson["wx_unionid"] = wx_unionid;
}
res.userInfo = await vk.baseDao.updateAndReturn({
dbName: "uni-id-users",
whereJson: {
_id: res.uid
},
dataJson
});
}
// 日志服务
const loginLogService = vk.require("service/user/util/login_log");
await loginLogService.add({
type: res.type,
login_type: "weixinPhoneNumber",
user_id: res.uid,
context: originalParam.context
}, util);
// 业务逻辑结束-----------------------------------------------------------
return res;
}
}
@@ -0,0 +1,24 @@
module.exports = {
/**
* 用户退出登录
* @url user/pub/logout 前端调用的url参数地址
* @description 用户退出登录
* res 返回参数说明
* @param {Number} code 错误码,0表示成功
* @param {String} msg 详细信息
*/
main: async (event) => {
let { util } = event;
let { uniID } = util;
let res = {};
// 业务逻辑开始-----------------------------------------------------------
if(event.uniIdToken){
res = await uniID.logout(event.uniIdToken);
}else{
res.code = 0;
res.msg = "未登录";
}
// 业务逻辑结束-----------------------------------------------------------
return res;
}
}
@@ -0,0 +1,61 @@
module.exports = {
/**
* 用户注册(账号+密码)
* @url user/pub/register 前端调用的url参数地址
* @description 用户注册(账号+密码)
* data 请求参数 说明
* @param {String} username 用户名,唯一
* @param {String} password 密码
* res 返回参数说明
* @param {Number} code 错误码,0表示成功
* @param {String} msg 详细信息
* @param {String} token 注册完成自动登录之后返回的token信息
* @param {String} tokenExpired token过期时间
* @param {Object} userInfo 用户信息
*/
main: async (event) => {
let { data = {}, userInfo, util, originalParam } = event;
let { uniID, config, pubFun, vk, db, _ } = util;
let { uid } = data;
let res = { code: -1, msg: '' };
// 业务逻辑开始-----------------------------------------------------------
if (typeof data.username === "number") data.username = String(data.username).trim();
if (typeof data.password === "number") data.password = String(data.password).trim();
let {
username,
password,
needPermission,
myInviteCode
} = data;
// 验证规则开始 -----------------------------------------------------------
let rules = {
username: [
{ required: true, validator: vk.pubfn.validator("username"), message: '用户名以字母开头,长度在3~32之间,只能包含字母、数字和下划线', trigger: ['blur', 'change'] }
],
password: [
{ validator: vk.pubfn.validator("password"), message: '密码长度在6~18之间,只能包含字母、数字和下划线', trigger: 'blur' }
]
};
// 验证规则结束 -----------------------------------------------------------
// 开始进行验证
let formRulesRes = vk.pubfn.formValidate({
data: data,
rules: rules
});
if (formRulesRes.code !== 0) {
// 表单验证未通过
return formRulesRes;
}
// 表单验证通过,下面写自己的业务逻辑
res = await uniID.register({
username,
password,
needPermission,
myInviteCode
});
// 业务逻辑结束-----------------------------------------------------------
return res;
}
}
@@ -0,0 +1,52 @@
module.exports = {
/**
* 根据邮箱+验证码重置密码
* @url user/pub/resetPasswordByEmail 前端调用的url参数地址
* data 请求参数 说明
* @param {String} password 重置后的密码
* @param {String} code 验证码
* @param {String} email 邮箱号码
* res 返回参数说明
* @param {Number} code 错误码,0表示成功
* @param {String} msg 详细信息
*/
main: async (event) => {
let { data = {}, util, originalParam } = event;
let { uniID, config, pubFun, vk , db, _ } = util;
let {
_id,
password,
code,
email
} = data;
let res = { code : -1, msg : '' };
// 业务逻辑开始-----------------------------------------------------------
// 可写与数据库的交互逻辑等等
// 检查验证码是否正确
res = await uniID.verifyCode({
email,
code,
type: 'reset-pwd', // 自2022-08-13起,以从reset改为reset-pwd,与官方uni-id对齐
});
if(res.code !== 0){
return res;
}
// 根据email查询用户是否存在
let userInfo = await vk.baseDao.findByWhereJson({
dbName:"uni-id-users",
whereJson:{
email
}
});
if(!userInfo){
return { code : -1, msg : '邮箱号未注册!' };
}
// 重置密码
res = await uniID.resetPwd({
uid: userInfo._id,
password
});
// 业务逻辑结束-----------------------------------------------------------
return res;
}
}
@@ -0,0 +1,52 @@
module.exports = {
/**
* 根据手机+验证码重置密码
* @url user/pub/resetPasswordByMobile 前端调用的url参数地址
* data 请求参数 说明
* @param {String} password 重置后的密码
* @param {String} code 验证码
* @param {String} mobile 手机号
* res 返回参数说明
* @param {Number} code 错误码,0表示成功
* @param {String} msg 详细信息
*/
main: async (event) => {
let { data = {}, util, originalParam } = event;
let { uniID, config, pubFun, vk , db, _ } = util;
let {
_id,
password,
code,
mobile
} = data;
let res = { code : -1, msg : '' };
// 业务逻辑开始-----------------------------------------------------------
// 可写与数据库的交互逻辑等等
// 检查验证码是否正确
res = await uniID.verifyCode({
mobile,
code,
type: 'reset-pwd', // 自2022-08-13起,以从reset改为reset-pwd,与官方uni-id对齐
});
if(res.code !== 0){
return res;
}
// 根据mobile查询用户是否存在
let userInfo = await vk.baseDao.findByWhereJson({
dbName:"uni-id-users",
whereJson:{
mobile
}
});
if(!userInfo){
return { code : -1, msg : '手机号未注册!' };
}
// 重置密码
res = await uniID.resetPwd({
uid: userInfo._id,
password
});
// 业务逻辑结束-----------------------------------------------------------
return res;
}
}
@@ -0,0 +1,112 @@
var vkmail;
try {
vkmail = require('vk-mail');
} catch (err) {
console.error("请先添加公共模块:vk-mail(右键对应的云函数,点击管理公共模块或扩展库依赖,勾选vk-mail依赖)");
}
var everyDaylimit = 30; // 限制同一个邮箱每天次数
module.exports = {
/**
* 发送邮箱验证码
* @url user/pub/sendEmailCode 前端调用的url参数地址
* @description 发送邮箱验证码
* data 请求参数 说明
* @param {String} email 邮箱
* @param {String} type 验证码类型
* @param {String} serviceType 邮件服务类型,默认为qq
* @param {Boolean|String} checkUserExist 是否需要检测邮箱对应的账号是否存在或不存在,默认不检测,若为exists代表邮箱必须已注册,若为!exists代表邮箱必须未注册
* res 返回参数说明
* @param {Number} code 错误码,0表示成功
* @param {String} msg 详细信息
*/
main: async (event) => {
let { data = {}, util } = event;
let { uniID, config } = util;
let {
email,
type,
serviceType = "qq",
checkUserExist = false
} = data;
let res = { code: 0, msg: 'ok' };
// 业务逻辑开始-----------------------------------------------------------
if (!vkmail) {
return { code: -1, msg: "请先添加公共模块:vk-mail(右键对应的云函数,点击管理公共模块或扩展库依赖,勾选vk-mail依赖)" };
}
// 参数非空检测
let nullKey = vk.pubfn.isNullOneByObject({ email, type });
if (nullKey) return { code: -1, msg: '参数 ' + nullKey + ' 不能为空' };
if (!vk.pubfn.test(email, "email")) return { code: -1, msg: "邮箱格式错误" };
if (vk.pubfn.isNull(type)) return { code: -1, msg: "type不能为空" };
if (checkUserExist) {
let dcloud_appid = originalParam.context.appId;
// 检测邮箱对应的账号是否存在
let num = await vk.daoCenter.userDao.count({
email,
// dcloud_appid: _.or(_.eq(dcloud_appid), _.exists(false))
});
if (checkUserExist === true || checkUserExist === "exists") {
// 邮箱必须已注册用户,如果邮箱未注册,则不发短信
if (num === 0) {
return { code: -1, msg: `邮箱:${email} 未注册`, notExists: true };
}
} else if (checkUserExist === "!exists") {
// 邮箱必须未注册用户,如果邮箱已注册,则不发短信
if (num > 0) {
return { code: -1, msg: `邮箱:${email} 已注册`, exists: true };
}
}
}
// 针对同一个邮箱,发送验证码每天有次数限制
const cacheKey = `sys.sendEmailCode.everyDaylimit-${email}`;
let currNum = await vk.globalDataCache.get(cacheKey);
if (!currNum) currNum = 0;
if (currNum >= everyDaylimit) {
return { code: -1, msg: "此邮箱今日已超最大次数限制,请明日再试" };
}
let code = vk.pubfn.random(6, "0123456789");
let param = {
code,
type,
email
};
// 发送验证码开始
let emailConfig = config.vk.service.email;
// 如果配置设置了过期时间,则使用配置的过期时间,否则默认180秒
param.expiresIn = emailConfig.codeExpiresIn || 180;
let emailService = vkmail.createTransport({
"host": emailConfig[serviceType].host,
"port": emailConfig[serviceType].port,
"secure": emailConfig[serviceType].secure, // use SSL
"auth": emailConfig[serviceType].auth
});
try {
// 发送邮件
await emailService.sendMail({
"from": emailConfig[serviceType].auth.user,
"to": data.email,
"cc": emailConfig[serviceType].auth.user, // 由于邮件可能会被当成垃圾邮件,但只要把邮件抄送给自己一份,就不会被当成垃圾邮件。
"subject": data.subject || `验证码是${code}`, // 邮件的标题
"text": `您的验证码是${code},打死也不要告诉别人哦!`, // 邮件的内容
});
// 发送验证码成功后,设置验证码
await uniID.setVerifyCode(param);
currNum++;
let { startTime } = vk.pubfn.getDayOffsetStartAndEnd(1, new Date());
let second = vk.pubfn.toDecimal((startTime - Date.now()) / 1000, 0)
await vk.globalDataCache.set(cacheKey, currNum, second);
} catch (err) {
console.error(err);
return { code: -1, msg: "邮件发送失败", err };
}
// 发送验证码结束
// 业务逻辑结束-----------------------------------------------------------
return res;
}
}
@@ -0,0 +1,87 @@
var everyDaylimit = 12; // 限制同一个手机号每天次数
module.exports = {
/**
* 发送短信的验证码
* @url user/pub/sendSmsCode 前端调用的url参数地址
* @description 发送验证码
* data 请求参数 说明
* @param {String} mobile 手机号
* @param {String} type 验证码类型
* @param {Boolean|String} checkUserExist 是否需要检测手机号对应的账号是否存在或不存在,默认不检测,若为exists代表手机号必须已注册,若为!exists代表手机号必须未注册
*
* res 返回参数说明
* @param {Number} code 错误码,0表示成功
* @param {String} msg 详细信息
* @param {Object} requestRes 原始返回数据
* @param {Object} requestParam 包含服务供应商和发送的手机号
*/
main: async (event) => {
let {data = {}, userInfo, util, originalParam} = event;
let {uniID, config, pubFun, vk, db, _} = util;
let {uid} = data;
let res = {code: 0, msg: ''};
// 业务逻辑开始-----------------------------------------------------------
let {
mobile,
type,
checkUserExist = false
} = data;
if (vk.pubfn.isNull(mobile)) {
return {code: -1, msg: '手机号不能为空'};
}
if (!vk.pubfn.test(mobile, 'mobile')) {
return {code: -1, msg: "手机号格式错误"};
}
if (checkUserExist) {
let dcloud_appid = originalParam.context.appId;
// 检测手机号对应的账号是否存在
let num = await vk.daoCenter.userDao.count({
mobile,
// dcloud_appid: _.or(_.eq(dcloud_appid), _.exists(false))
});
if (checkUserExist === true || checkUserExist === "exists") {
// 手机号必须已注册用户,如果手机号未注册,则不发短信
if (num === 0) {
return {code: -1, msg: `手机号:${mobile} 未注册`, notExists: true};
}
} else if (checkUserExist === "!exists") {
// 手机号必须未注册用户,如果手机号已注册,则不发短信
if (num > 0) {
return {code: -1, msg: `手机号:${mobile} 已注册`, exists: true};
}
}
}
// 针对同一个手机号,发送验证码每天有次数限制
const cacheKey = `sys.sendSmsCode.everyDaylimit-${mobile}`;
let currNum = await vk.globalDataCache.get(cacheKey);
if (!currNum) currNum = 0;
if (currNum >= everyDaylimit) {
return {code: -1, msg: "此手机号今日已超最大次数限制,请明日再试"};
}
let code = vk.pubfn.random(6, "0123456789");
let provider = "unicloud";
if (vk.pubfn.getData(config, "vk.service.sms.aliyun.enable")) {
provider = "aliyun";
}
res = await vk.system.smsUtil.sendSmsVerifyCode({
provider,
code,
type,
phone: mobile,
expiresIn: 180, // 验证码实际有效时间,必须是60的倍数
});
if (res.code == 0) {
currNum++;
let {startTime} = vk.pubfn.getDayOffsetStartAndEnd(1, new Date());
let second = vk.pubfn.toDecimal((startTime - Date.now()) / 1000, 0)
await vk.globalDataCache.set(cacheKey, currNum, second);
await vk.globalDataCache.set(`${type}-${mobile}-code`, code, 180)
}
// 业务逻辑结束-----------------------------------------------------------
return res;
}
}