【经验分享】【微信小程序】简单在已有账号系统基础上引入uni-id使用clientDB的权限管理
折腾了一下午,终于给我的小程序成功引入了uni-id,之前每次想到要迁移就头大,今天终于下定决心换到clientDB了,也是为了减少云函数的用量。在这里就简单分享一下校验。
参考:https://en.uniapp.dcloud.io/uniCloud/uni-id.html(这个教程是老的uni-id,对我够用了,不想迁移到新版)
安装uni-id
- 导入插件。
- 写config-center的配置,复制上面教程的文件只需要改mp-weixin.oauth.weixin内的appid和secret。
- 创建uni-id-users和opendb-verify-codes表,后者用于验证码对小程序其实不必要,但我不知道删了能不能跑。
以上几步可以简单的安装好uni-id,然后便是迁移工作了。
迁移
首先我希望我的权限是依赖于openid的,所以我便利了我自己搭建的账号数据库,并逐条写入uni-id-user表,只需要写两个字段_id和wx_openid.mp-weixin,这两个字段同时设置为原先的openid。
登录
之后按照文档的微信登录段,新建一个云函数,内容如下
const uniID = require("uni-id");
exports.main = async function (event, context) {
// const res = await uniID.loginByWeixin(event.code)
const res = await uniID.loginByWeixin({
code: event.code,
});
return res;
};
注意这个云函数要选择公共依赖uni-id,安装的uni-id也需要选择公共依赖uni-open-bridge-common(我不知道为什么默认没选)。
以上是官方文档的,我将其修改为了
'use strict';
const uniID = require('uni-id');
const db = uniCloud.database();
const dbUID = db.collection('uni-id-users');
exports.main = async function (event) {
const res = await uniID.loginByWeixin({ code: event.code });
if (res.uid !== res.openid) {
await dbUID.doc(res.openid).set((await dbUID.doc(res.uid).field({ _id: false }).get()).data[0]);
await dbUID.doc(res.uid).remove();
res.uid = res.openid;
res.tokenExpired = 0;
}
return res;
};
因为默认第一次登录的账户他会新建一条记录,而这条记录的uid不是原有的openid,所以需要手动修改。
同时修改以后原先的token是作废的,所以使用tokenExpired=0来让客户端手动重新二次调用。(每次都需要上传一个只能用一次的code)
然后我客户端用这个云函数取代了我原先用于获取openid的云函数,因为这个也会返回openid,正好还能登录一举两得。
角色
另一方面为了用上uni-id的角色系统,我还在我自己的关于权限变更的云函数里同时变更uni-id-users表的role字段,这样clientDB也可以用角色控制了xd
折腾了一下午,终于给我的小程序成功引入了uni-id,之前每次想到要迁移就头大,今天终于下定决心换到clientDB了,也是为了减少云函数的用量。在这里就简单分享一下校验。
参考:https://en.uniapp.dcloud.io/uniCloud/uni-id.html(这个教程是老的uni-id,对我够用了,不想迁移到新版)
安装uni-id
- 导入插件。
- 写config-center的配置,复制上面教程的文件只需要改mp-weixin.oauth.weixin内的appid和secret。
- 创建uni-id-users和opendb-verify-codes表,后者用于验证码对小程序其实不必要,但我不知道删了能不能跑。
以上几步可以简单的安装好uni-id,然后便是迁移工作了。
迁移
首先我希望我的权限是依赖于openid的,所以我便利了我自己搭建的账号数据库,并逐条写入uni-id-user表,只需要写两个字段_id和wx_openid.mp-weixin,这两个字段同时设置为原先的openid。
登录
之后按照文档的微信登录段,新建一个云函数,内容如下
const uniID = require("uni-id");
exports.main = async function (event, context) {
// const res = await uniID.loginByWeixin(event.code)
const res = await uniID.loginByWeixin({
code: event.code,
});
return res;
};
注意这个云函数要选择公共依赖uni-id,安装的uni-id也需要选择公共依赖uni-open-bridge-common(我不知道为什么默认没选)。
以上是官方文档的,我将其修改为了
'use strict';
const uniID = require('uni-id');
const db = uniCloud.database();
const dbUID = db.collection('uni-id-users');
exports.main = async function (event) {
const res = await uniID.loginByWeixin({ code: event.code });
if (res.uid !== res.openid) {
await dbUID.doc(res.openid).set((await dbUID.doc(res.uid).field({ _id: false }).get()).data[0]);
await dbUID.doc(res.uid).remove();
res.uid = res.openid;
res.tokenExpired = 0;
}
return res;
};
因为默认第一次登录的账户他会新建一条记录,而这条记录的uid不是原有的openid,所以需要手动修改。
同时修改以后原先的token是作废的,所以使用tokenExpired=0来让客户端手动重新二次调用。(每次都需要上传一个只能用一次的code)
然后我客户端用这个云函数取代了我原先用于获取openid的云函数,因为这个也会返回openid,正好还能登录一举两得。
角色
另一方面为了用上uni-id的角色系统,我还在我自己的关于权限变更的云函数里同时变更uni-id-users表的role字段,这样clientDB也可以用角色控制了xd
收起阅读 »关于uni手机号一键登录价格调整的公告
因三大运营商的集体涨价,自2024年10月01日零时起,uni手机号一键登录服务的价格将进行调整。
这是一次全行业涨价,其他如阿里云一键登陆等也都涨价了。
调整后的价格如下所示:
按量计费
按量价格为:0.03元/次
资源包
规格(次数) | 价格(元) | 相对按量计费单价(元/次) |
---|---|---|
1万 | 290 | 0.029 |
2万 | 570 | 0.0285 |
5万 | 1400 | 0.028 |
10万 | 2750 | 0.0275 |
20万 | 5400 | 0.027 |
50万 | 13250 | 0.0265 |
100万 | 26000 | 0.026 |
200万 | 51000 | 0.0255 |
500万 | 125000 | 0.025 |
注意:资源包自购买之日起6个月内有效。
调价后,uni一键登录依然比其他云厂商的竞品便宜
- 阿里云
阿里云的号码验证(一键登录)按量计费起步定价为4分/次
,比uni一键登录的3分/次
贵了33%;
阿里云一键登录需月消耗需超过500万次后,才可达到3分/次
的单价,实际上大多开发者的App,月消耗是远到不了500万次的,详见阿里云 - 号码认证 - 产品计费
阿里云的套餐包,单价也比uni一键登录要贵很多,以阿里云官网单价最低的100万次套餐为例:
100万次的一键登录套餐,阿里云比uni一键登录要贵6500元:
厂商 | 价格(元) | 单价(元/次) |
---|---|---|
阿里云 | 32500 | 0.0325 |
uni一键登录 | 26000 | 0.026 |
实际上,阿里云100万次的套餐包,甚至比uni一键登录的按量计费还要贵不少。
- 腾讯云
腾讯云的号码验证(一键登录)不支持在线自助开通,需要先填写申请表单,等待审核,且腾讯云仅支持按量计费,不支持资源包。
腾讯云的按量定价和阿里云一样,也是4分/次
起步,同样比uni一键登录要贵33%;
即便加上uniCloud云函数的费用,uni一键登陆也更便宜。详见
总体而言,不管是按量计费,还是套餐包,uni一键登录都对中小开发者更为友好!
调价后,uni一键登录依然比短信更具性价比
uni一键登录相比短信验证码,具备明显的优势:
- 用户体验好:一键登录,无需等待和复制短信验证码,能有效降低用户流失率,提升用户注册转换率;
- 便宜:虽然调价了,但uni一键登录,平均每次验证仅需2分多,比短信验证码更为便宜;
- 安全:采用运营商网关认证,避免短信劫持,有效提升安全性
相对短信验证码,我们更推荐开发者使用一键登录。
因三大运营商的集体涨价,自2024年10月01日零时起,uni手机号一键登录服务的价格将进行调整。
这是一次全行业涨价,其他如阿里云一键登陆等也都涨价了。
调整后的价格如下所示:
按量计费
按量价格为:0.03元/次
资源包
规格(次数) | 价格(元) | 相对按量计费单价(元/次) |
---|---|---|
1万 | 290 | 0.029 |
2万 | 570 | 0.0285 |
5万 | 1400 | 0.028 |
10万 | 2750 | 0.0275 |
20万 | 5400 | 0.027 |
50万 | 13250 | 0.0265 |
100万 | 26000 | 0.026 |
200万 | 51000 | 0.0255 |
500万 | 125000 | 0.025 |
注意:资源包自购买之日起6个月内有效。
调价后,uni一键登录依然比其他云厂商的竞品便宜
- 阿里云
阿里云的号码验证(一键登录)按量计费起步定价为4分/次
,比uni一键登录的3分/次
贵了33%;
阿里云一键登录需月消耗需超过500万次后,才可达到3分/次
的单价,实际上大多开发者的App,月消耗是远到不了500万次的,详见阿里云 - 号码认证 - 产品计费
阿里云的套餐包,单价也比uni一键登录要贵很多,以阿里云官网单价最低的100万次套餐为例:
100万次的一键登录套餐,阿里云比uni一键登录要贵6500元:
厂商 | 价格(元) | 单价(元/次) |
---|---|---|
阿里云 | 32500 | 0.0325 |
uni一键登录 | 26000 | 0.026 |
实际上,阿里云100万次的套餐包,甚至比uni一键登录的按量计费还要贵不少。
- 腾讯云
腾讯云的号码验证(一键登录)不支持在线自助开通,需要先填写申请表单,等待审核,且腾讯云仅支持按量计费,不支持资源包。
腾讯云的按量定价和阿里云一样,也是4分/次
起步,同样比uni一键登录要贵33%;
即便加上uniCloud云函数的费用,uni一键登陆也更便宜。详见
总体而言,不管是按量计费,还是套餐包,uni一键登录都对中小开发者更为友好!
调价后,uni一键登录依然比短信更具性价比
uni一键登录相比短信验证码,具备明显的优势:
- 用户体验好:一键登录,无需等待和复制短信验证码,能有效降低用户流失率,提升用户注册转换率;
- 便宜:虽然调价了,但uni一键登录,平均每次验证仅需2分多,比短信验证码更为便宜;
- 安全:采用运营商网关认证,避免短信劫持,有效提升安全性
相对短信验证码,我们更推荐开发者使用一键登录。
收起阅读 »【报Bug】我之前发布的文章,为啥下面的评论,你们官方都删除了
【报Bug】我之前发布的文章,为啥下面的评论,你们官方都删除了???
【报Bug】我之前发布的文章,为啥下面的评论,你们官方都删除了???
uniCloud 外部系统联登 注册功能 C# 完整示例
APP端
this.$http
.post(`/uniCloudRegister`, {
clientInfo:JSON.stringify(uni.getSystemInfoSync())
})
.then(res =>{
uni.stopPullDownRefresh()
uni.hideNavigationBarLoading()
console.info(res)
}).catch(err => {
console.error(err)
uni.stopPullDownRefresh()
uni.hideNavigationBarLoading()
})
后台接口 UserController:ApiController
ConstantsConstants//业务系统登录后才需要联登到 uniCloud,所以不需要在注册时执行,而是单独给出了注册功能的接口
[HttpPost]
[Route("uniCloudRegister")]
public async Task<HttpResponseMessage> uniCloudRegister([FromBody] UniClient client)
{
HttpResponseMessage response = null;
try
{
//此为验证当前系统token并转为 user类的工具,这里就不给出详细示例了
var ui = JwtHelper.AnalysisToken(HttpContext.Current.Request);
//见 Utils
string uniAppUserName = Utils.GetExternalUid(ui);
string nonce = Utils.GetNonce();
// 获取当前时间戳(精确到毫秒)
long timestamp = Utils.GetNowTimeStamp();
Dictionary<string, string> paramsDictionary = new Dictionary<string, string>
{
{ "externalUid", uniAppUserName},
{ "nickname", ui.Name},
};
//签名算法见 Utils
string signature = Utils.GetSignature(paramsDictionary, nonce, timestamp);
//client.clientInfo 为JSON.stringify(uni.getSystemInfoSync()) 这里再转回json对象
var cliInfo = JsonConvert.DeserializeObject<JObject>(client.clientInfo);
Dictionary<string, object> param = new Dictionary<string, object>();
param.Add("clientInfo", cliInfo);
param.Add("uniIdToken", "");
param.Add("params", paramsDictionary);
// 将对象序列化为JSON字符串
string jsonContent = JsonConvert.SerializeObject(param);
Dictionary<string, string> headers = new Dictionary<string, string>()
{
{"uni-id-nonce",nonce },
{"uni-id-timestamp",timestamp + ""},
{"uni-id-signature",signature}
};
var res = await HttpHelper.SendPostAsync(Constants.PushRegister, jsonContent, headers);
if (res.Count > 0)
{
response = Request.CreateResponse(System.Net.HttpStatusCode.OK, res);
}
else
{
response = Request.CreateResponse(System.Net.HttpStatusCode.BadRequest, "error");
}
}
catch (Exception ex)
{
response = Request.CreateErrorResponse(System.Net.HttpStatusCode.InternalServerError, "error");
}
return response;
}
Constants
//uniapp 外部系统联登 https://doc.dcloud.net.cn/uniCloud/uni-id/cloud-object.html#external
public static readonly string PushUrl = "you url";
//uniapp 注册
public static readonly string PushRegister = PushUrl + "externalRegister";
//uniapp 登录
public static readonly string PushLogin = PushUrl + "externalLogin";
//uniapp 修改信息
public static readonly string PushUpdateUser = PushUrl + "updateUserInfoByExternal ";
UniClient
public class UniClient
{
public string clientInfo { get; set; }
public string uniIdToken { get; set; }
}
Utils
/// <summary>
/// 根据用户信息 获取uniapp的uid, 这个就根据自己的业务来处理
/// </summary>
/// <param name="ui"></param>
/// <returns></returns>
public static string GetExternalUid(UserInfo ui)
{
//分别为角色ID、 用户ID和用户账号
return ui.Role + "_" + ui.Id + "_" + ui.userName;
}
//获取随机字符串 这里其实可以固定返回一组字符串
public static string GetNonce()
{
return GenerateRandomStringLinq(8);
}
private static string GenerateRandomStringLinq(int length)
{
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz";
Random random = new Random();
return new string(Enumerable.Repeat(chars, length)
.Select(s => s[random.Next(s.Length)]).ToArray());
}
/// <summary>
/// uniapp 鉴权签名算法
/// </summary>
/// <param name="parameters"></param>
/// <param name="nonce"></param>
/// <param name="timestamp"></param>
/// <returns></returns>
public static string GetSignature(Dictionary<string, string> parameters, string nonce, long timestamp)
{
string paramsStr = GetParamsString(parameters);
using (HMACSHA256 hmacSha256 = new HMACSHA256(Encoding.UTF8.GetBytes(Constants.RequestAuthSecret + nonce)))
{
string message = timestamp.ToString() + paramsStr;
byte[] messageBytes = Encoding.UTF8.GetBytes(message);
byte[] hashBytes = hmacSha256.ComputeHash(messageBytes);
return ByteArrayToHexString(hashBytes).ToUpper();
}
}
/// <summary>
/// 获取当前时间戳 单位毫秒
/// </summary>
/// <returns></returns>
public static long GetNowTimeStamp()
{
return (long)DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalMilliseconds;
}
private static string GetParamsString(Dictionary<string, string> parameters)
{
var keys = new List<string>(parameters.Keys);
keys.Sort();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < keys.Count; i++)
{
if (i != 0)
{
sb.Append("&");
}
sb.Append(keys[i]).Append("=").Append(parameters[keys[i]]);
}
return sb.ToString();
}
private static string ByteArrayToHexString(byte[] bytes)
{
StringBuilder sb = new StringBuilder();
foreach (byte b in bytes)
{
string hex = b.ToString("x2");
sb.Append(hex);
}
return sb.ToString();
}
HttpHelper
/// <summary>
/// POST异步请求
///
/// </summary>
/// <param name="url">请求url</param>
/// <param name="jsonContent"></param>
/// <param name="headers"></param>
/// <returns></returns>
// 发送POST请求的函数
public static async Task<JObject> SendPostAsync(string url, string jsonContent, Dictionary<string, string> headers = null)
{
using (var client = new HttpClient())
{
var jsonObject = new JObject();
if (headers != null)
{
foreach (KeyValuePair<string, string> header in headers)
{
client.DefaultRequestHeaders.Add(header.Key, header.Value);
}
}
try
{
// 创建一个HttpContent对象,用于发送JSON数据
var httpContent = new StringContent(jsonContent, Encoding.UTF8, "application/json");
// 发送POST请求
HttpResponseMessage response = await client.PostAsync(url, httpContent);
// 确保HTTP请求成功
//response.EnsureSuccessStatusCode();
// 读取响应内容
var responseBody = await response.Content.ReadAsStringAsync();
//LoggerHelper.Info("请求:" + url + ",参数:" + jsonContent + ",结果:" + responseBody);
jsonObject = JsonConvert.DeserializeObject<JObject>(responseBody);
}
catch (HttpRequestException e)
{
LoggerHelper.Error("请求失败!",e);
}
return jsonObject;
}
}
tips: 出现了 "clientInfo.uniPlatform" is required. 是没有传参数clientInfo
参考 官方文档
APP端
this.$http
.post(`/uniCloudRegister`, {
clientInfo:JSON.stringify(uni.getSystemInfoSync())
})
.then(res =>{
uni.stopPullDownRefresh()
uni.hideNavigationBarLoading()
console.info(res)
}).catch(err => {
console.error(err)
uni.stopPullDownRefresh()
uni.hideNavigationBarLoading()
})
后台接口 UserController:ApiController
ConstantsConstants//业务系统登录后才需要联登到 uniCloud,所以不需要在注册时执行,而是单独给出了注册功能的接口
[HttpPost]
[Route("uniCloudRegister")]
public async Task<HttpResponseMessage> uniCloudRegister([FromBody] UniClient client)
{
HttpResponseMessage response = null;
try
{
//此为验证当前系统token并转为 user类的工具,这里就不给出详细示例了
var ui = JwtHelper.AnalysisToken(HttpContext.Current.Request);
//见 Utils
string uniAppUserName = Utils.GetExternalUid(ui);
string nonce = Utils.GetNonce();
// 获取当前时间戳(精确到毫秒)
long timestamp = Utils.GetNowTimeStamp();
Dictionary<string, string> paramsDictionary = new Dictionary<string, string>
{
{ "externalUid", uniAppUserName},
{ "nickname", ui.Name},
};
//签名算法见 Utils
string signature = Utils.GetSignature(paramsDictionary, nonce, timestamp);
//client.clientInfo 为JSON.stringify(uni.getSystemInfoSync()) 这里再转回json对象
var cliInfo = JsonConvert.DeserializeObject<JObject>(client.clientInfo);
Dictionary<string, object> param = new Dictionary<string, object>();
param.Add("clientInfo", cliInfo);
param.Add("uniIdToken", "");
param.Add("params", paramsDictionary);
// 将对象序列化为JSON字符串
string jsonContent = JsonConvert.SerializeObject(param);
Dictionary<string, string> headers = new Dictionary<string, string>()
{
{"uni-id-nonce",nonce },
{"uni-id-timestamp",timestamp + ""},
{"uni-id-signature",signature}
};
var res = await HttpHelper.SendPostAsync(Constants.PushRegister, jsonContent, headers);
if (res.Count > 0)
{
response = Request.CreateResponse(System.Net.HttpStatusCode.OK, res);
}
else
{
response = Request.CreateResponse(System.Net.HttpStatusCode.BadRequest, "error");
}
}
catch (Exception ex)
{
response = Request.CreateErrorResponse(System.Net.HttpStatusCode.InternalServerError, "error");
}
return response;
}
Constants
//uniapp 外部系统联登 https://doc.dcloud.net.cn/uniCloud/uni-id/cloud-object.html#external
public static readonly string PushUrl = "you url";
//uniapp 注册
public static readonly string PushRegister = PushUrl + "externalRegister";
//uniapp 登录
public static readonly string PushLogin = PushUrl + "externalLogin";
//uniapp 修改信息
public static readonly string PushUpdateUser = PushUrl + "updateUserInfoByExternal ";
UniClient
public class UniClient
{
public string clientInfo { get; set; }
public string uniIdToken { get; set; }
}
Utils
/// <summary>
/// 根据用户信息 获取uniapp的uid, 这个就根据自己的业务来处理
/// </summary>
/// <param name="ui"></param>
/// <returns></returns>
public static string GetExternalUid(UserInfo ui)
{
//分别为角色ID、 用户ID和用户账号
return ui.Role + "_" + ui.Id + "_" + ui.userName;
}
//获取随机字符串 这里其实可以固定返回一组字符串
public static string GetNonce()
{
return GenerateRandomStringLinq(8);
}
private static string GenerateRandomStringLinq(int length)
{
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz";
Random random = new Random();
return new string(Enumerable.Repeat(chars, length)
.Select(s => s[random.Next(s.Length)]).ToArray());
}
/// <summary>
/// uniapp 鉴权签名算法
/// </summary>
/// <param name="parameters"></param>
/// <param name="nonce"></param>
/// <param name="timestamp"></param>
/// <returns></returns>
public static string GetSignature(Dictionary<string, string> parameters, string nonce, long timestamp)
{
string paramsStr = GetParamsString(parameters);
using (HMACSHA256 hmacSha256 = new HMACSHA256(Encoding.UTF8.GetBytes(Constants.RequestAuthSecret + nonce)))
{
string message = timestamp.ToString() + paramsStr;
byte[] messageBytes = Encoding.UTF8.GetBytes(message);
byte[] hashBytes = hmacSha256.ComputeHash(messageBytes);
return ByteArrayToHexString(hashBytes).ToUpper();
}
}
/// <summary>
/// 获取当前时间戳 单位毫秒
/// </summary>
/// <returns></returns>
public static long GetNowTimeStamp()
{
return (long)DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalMilliseconds;
}
private static string GetParamsString(Dictionary<string, string> parameters)
{
var keys = new List<string>(parameters.Keys);
keys.Sort();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < keys.Count; i++)
{
if (i != 0)
{
sb.Append("&");
}
sb.Append(keys[i]).Append("=").Append(parameters[keys[i]]);
}
return sb.ToString();
}
private static string ByteArrayToHexString(byte[] bytes)
{
StringBuilder sb = new StringBuilder();
foreach (byte b in bytes)
{
string hex = b.ToString("x2");
sb.Append(hex);
}
return sb.ToString();
}
HttpHelper
/// <summary>
/// POST异步请求
///
/// </summary>
/// <param name="url">请求url</param>
/// <param name="jsonContent"></param>
/// <param name="headers"></param>
/// <returns></returns>
// 发送POST请求的函数
public static async Task<JObject> SendPostAsync(string url, string jsonContent, Dictionary<string, string> headers = null)
{
using (var client = new HttpClient())
{
var jsonObject = new JObject();
if (headers != null)
{
foreach (KeyValuePair<string, string> header in headers)
{
client.DefaultRequestHeaders.Add(header.Key, header.Value);
}
}
try
{
// 创建一个HttpContent对象,用于发送JSON数据
var httpContent = new StringContent(jsonContent, Encoding.UTF8, "application/json");
// 发送POST请求
HttpResponseMessage response = await client.PostAsync(url, httpContent);
// 确保HTTP请求成功
//response.EnsureSuccessStatusCode();
// 读取响应内容
var responseBody = await response.Content.ReadAsStringAsync();
//LoggerHelper.Info("请求:" + url + ",参数:" + jsonContent + ",结果:" + responseBody);
jsonObject = JsonConvert.DeserializeObject<JObject>(responseBody);
}
catch (HttpRequestException e)
{
LoggerHelper.Error("请求失败!",e);
}
return jsonObject;
}
}
tips: 出现了 "clientInfo.uniPlatform" is required. 是没有传参数clientInfo
参考 官方文档
收起阅读 »UID短信无法登录问题,短信验证无法登录问题。
相信很多朋友在建立项目是碰见了这个问题:使用uid短信登陆,一切都弄好了,但是一运行就出现;
未找到scene=login-by-sms,的短信模版templateId。\n已启动测试模式,直接使用:123456作为短信验证码即可。\n如果是正式项目,请在路径:/common/uni-config-center/uni-id/config.json中service->sms中配置密钥等信息,
最后我试了很多遍,发现问题在这里;
官方在指引文件中给的方法是在UID的设置文件config中,选择填写
"service": {
"sms": {
"name": "模板名称",
"codeExpiresIn": 180,
"scene": {
"bind-mobile-by-sms": {
"templateId": "模板ID",
"codeExpiresIn": 240
}
}
}
}
但其实scene是场景的意思,也就是说,SMS应用场景有可能不止一个,而其中的bind-mobile-by-sms意思是指通过短信绑定手机号,并不是我们要的短信登陆,所以才导致我们填写完了相关信息却在登陆环节一直没有识别,导致出现未找到登陆对应的场景。以下借用其他朋友的编码解决该问题。
"service": {
"sms": {
"name": 模板名称",
"codeExpiresIn": 180,
"scene": {
"login-by-sms": {
"templateId": "模板id",
"codeExpiresIn": 240
},
"bind-mobile-by-sms": {
"templateId": "模板id",
"codeExpiresIn": 240
},
"reset-pwd-by-sms": {
"templateId": "模板id",
"codeExpiresIn": 240
}
}
},
"univerify": {
"appid": ""
}
}
}
这样我们就在一个短信模板下加了三个场景,分别是登陆,绑定,重置密码。如果需要使用不同ID的短息模板的话,去自己服务器相关页面设置就好。
亲测有效。
相信很多朋友在建立项目是碰见了这个问题:使用uid短信登陆,一切都弄好了,但是一运行就出现;
未找到scene=login-by-sms,的短信模版templateId。\n已启动测试模式,直接使用:123456作为短信验证码即可。\n如果是正式项目,请在路径:/common/uni-config-center/uni-id/config.json中service->sms中配置密钥等信息,
最后我试了很多遍,发现问题在这里;
官方在指引文件中给的方法是在UID的设置文件config中,选择填写
"service": {
"sms": {
"name": "模板名称",
"codeExpiresIn": 180,
"scene": {
"bind-mobile-by-sms": {
"templateId": "模板ID",
"codeExpiresIn": 240
}
}
}
}
但其实scene是场景的意思,也就是说,SMS应用场景有可能不止一个,而其中的bind-mobile-by-sms意思是指通过短信绑定手机号,并不是我们要的短信登陆,所以才导致我们填写完了相关信息却在登陆环节一直没有识别,导致出现未找到登陆对应的场景。以下借用其他朋友的编码解决该问题。
"service": {
"sms": {
"name": 模板名称",
"codeExpiresIn": 180,
"scene": {
"login-by-sms": {
"templateId": "模板id",
"codeExpiresIn": 240
},
"bind-mobile-by-sms": {
"templateId": "模板id",
"codeExpiresIn": 240
},
"reset-pwd-by-sms": {
"templateId": "模板id",
"codeExpiresIn": 240
}
}
},
"univerify": {
"appid": ""
}
}
}
这样我们就在一个短信模板下加了三个场景,分别是登陆,绑定,重置密码。如果需要使用不同ID的短息模板的话,去自己服务器相关页面设置就好。
亲测有效。
记一次使用支付宝云函数踩的坑:Error:用户函数代码语法或逻辑异常,日志错误信息“invalid ELF header”
在云函数使用了bcrypt包
const bcrypt = require("bcrypt");
本地调试没有任何问题,云端调试开始报错:
Error: [login]: 用户函数代码语法或逻辑异常
函数运行日志报错:
... node_modules/bcrypt/lib/binding/napi-v3/bcrypt_lib.node: invalid ELF header ...
解决:
询问ai:(粘贴整段报错)
您遇到的错误信息与Node.js应用程序中的bcrypt模块有关,这个模块通常用于密码的散列和加密。错误信息“invalid ELF header”表明模块尝试加载的二进制文件(bcrypt_lib.node)存在问题。这个问题可能由以下几个原因引起:
架构不兼容:二进制文件可能为与您运行系统不同的CPU架构编译。例如,如果二进制文件是为x64架构编译的,但在ARM架构的系统上运行,就会发生这个错误。
Node ABI版本不匹配:二进制文件的Node应用二进制接口(ABI)版本可能与您使用的Node.js运行时的ABI版本不匹配。这通常在切换Node.js版本后没有重新编译本地模块时发生。二进制文件损坏或不正确:二进制文件可能已损坏,或者可能不是适用于您系统的正确文件。
ai没有给出具体可行的方案,继续询问:“换一个架构兼容的包”,回答:
安装预编译的二进制版本:一些包提供了预编译的二进制版本,这些版本可以在不同的架构上运行。尝试使用这些预编译版本可能会解决问题。例如,对于bcrypt,您可以尝试安装bcryptjs,这是一个纯JavaScript实现的bcrypt替代品,不需要编译
更换为 bcryptjs 成功解决问题
const bcrypt = require("bcryptjs");
在云函数使用了bcrypt包
const bcrypt = require("bcrypt");
本地调试没有任何问题,云端调试开始报错:
Error: [login]: 用户函数代码语法或逻辑异常
函数运行日志报错:
... node_modules/bcrypt/lib/binding/napi-v3/bcrypt_lib.node: invalid ELF header ...
解决:
询问ai:(粘贴整段报错)
您遇到的错误信息与Node.js应用程序中的bcrypt模块有关,这个模块通常用于密码的散列和加密。错误信息“invalid ELF header”表明模块尝试加载的二进制文件(bcrypt_lib.node)存在问题。这个问题可能由以下几个原因引起:
架构不兼容:二进制文件可能为与您运行系统不同的CPU架构编译。例如,如果二进制文件是为x64架构编译的,但在ARM架构的系统上运行,就会发生这个错误。
Node ABI版本不匹配:二进制文件的Node应用二进制接口(ABI)版本可能与您使用的Node.js运行时的ABI版本不匹配。这通常在切换Node.js版本后没有重新编译本地模块时发生。二进制文件损坏或不正确:二进制文件可能已损坏,或者可能不是适用于您系统的正确文件。
ai没有给出具体可行的方案,继续询问:“换一个架构兼容的包”,回答:
安装预编译的二进制版本:一些包提供了预编译的二进制版本,这些版本可以在不同的架构上运行。尝试使用这些预编译版本可能会解决问题。例如,对于bcrypt,您可以尝试安装bcryptjs,这是一个纯JavaScript实现的bcrypt替代品,不需要编译
更换为 bcryptjs 成功解决问题
const bcrypt = require("bcryptjs");
收起阅读 »
我开源了一款高颜值云端一体的项目。欢迎体验!!!
项目介绍
旅拍路书:旅行爱好者的专属记录伙伴,基于uniCloud + vue3的全栈项目,包括用户登录,更新个人信息,富文本编辑,分类管理以及智能助手等功能的项目。
预览
h5端扫码预览:
小程序扫码预览:
微信小程序提交审核未通过(你的小程序涉及用户自行生成内容(文字、图片、音/)的记录、分享,属社交-笔记范畴,为个人主体小程序未开放类目,建议申请企业主体小程序。)目前只有体验版,扫码申请体验权限,秒通过
直接链接访问,移动端项目需打开控制台调至移动端模式预览:
- <https://env-00jxgo74vwaq-static.normal.cloudstatic.cn/index.html#/>
技术栈
- vite
- vue3
- ts
- uniCloud
- wotDesignUni
- sp-editor
环境要求
- node > 16.20.0
- npm > 8.19.4
- HBuilder X > 4.07
部署
- Serverless 服务
- 云原生容器平台
部分截图
源码地址 免费开源哦!!!
项目介绍
旅拍路书:旅行爱好者的专属记录伙伴,基于uniCloud + vue3的全栈项目,包括用户登录,更新个人信息,富文本编辑,分类管理以及智能助手等功能的项目。
预览
h5端扫码预览:
小程序扫码预览:
微信小程序提交审核未通过(你的小程序涉及用户自行生成内容(文字、图片、音/)的记录、分享,属社交-笔记范畴,为个人主体小程序未开放类目,建议申请企业主体小程序。)目前只有体验版,扫码申请体验权限,秒通过
直接链接访问,移动端项目需打开控制台调至移动端模式预览:
- <https://env-00jxgo74vwaq-static.normal.cloudstatic.cn/index.html#/>
技术栈
- vite
- vue3
- ts
- uniCloud
- wotDesignUni
- sp-editor
环境要求
- node > 16.20.0
- npm > 8.19.4
- HBuilder X > 4.07
部署
- Serverless 服务
- 云原生容器平台
部分截图
源码地址 免费开源哦!!!
https://ext.dcloud.net.cn/plugin?id=18177#detail
收起阅读 »抖音火山引擎语音合成代码
// 抖音火山引擎语音合成
function makerDouyinVoice(content) {
const host = "https://openspeech.bytedance.com/api/v1/tts"
const header = {
"Authorization": "Bearer;Uwn******************iw0V4" // 换成自己的 token
}
const requestBody = {
"app": {
"appid": "123456", //换成自己的 抖音火山引擎账号 id
"token": "Uwn******************iw0V4", // 换成自己的 token
"cluster": "****", //换成自己的 cluster
},
"user": {
"uid": content.uid
},
"audio": {
"voice_type": "BV027_streaming", // 角色
"encoding": "mp3",
"compression_rate": 1,
"rate": 24000,
"speed_ratio": 0.8, // 语速
"volume_ratio": 1.0,
"pitch_ratio": 1.0,
"emotion": "happy",
"language": "en" // 这里切换中英文
},
"request": {
"reqid": content.sentenceId,
"text": content.text,
"text_type": "plain",
"operation": "query",
"silence_duration": "125",
"with_frontend": "1",
"frontend_type": "unitTson",
"pure_english_opt": "1"
}
}
return uniCloud.httpclient.request(host, {
method: 'POST',
headers: header,
data: requestBody,
contentType: 'json',
dataType:'text', // 注意这个参数,要以字符串形式返回
timeout: 60000
})
}
//----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
// 云对象调用
async makeVoiceByDouyin(content) {
const voiceRes = await makerDouyinVoice(content)
console.log(voiceRes)
if (voiceRes.status == 200) {
// 火山引擎返回的数据是字符串 要转换成json对象
const voiceData = JSON.parse(voiceRes.data)
if(voiceData.code == 3000){
// 火山引擎返回的音频数据 base64 要转换成 Buffer
const voiceBuffer = Buffer.from(voiceData.data,'base64')
// 上传到云存储
const res = await uniCloud.uploadFile({
cloudPath: `${content.sentenceId}.mp3`,
fileContent: voiceBuffer
})
return res
}
return {
errCode:1,
errMsg:'语音合成失败'
}
}
return {
errCode:1,
errMsg: '合成失败'
}
}
// 抖音火山引擎语音合成
function makerDouyinVoice(content) {
const host = "https://openspeech.bytedance.com/api/v1/tts"
const header = {
"Authorization": "Bearer;Uwn******************iw0V4" // 换成自己的 token
}
const requestBody = {
"app": {
"appid": "123456", //换成自己的 抖音火山引擎账号 id
"token": "Uwn******************iw0V4", // 换成自己的 token
"cluster": "****", //换成自己的 cluster
},
"user": {
"uid": content.uid
},
"audio": {
"voice_type": "BV027_streaming", // 角色
"encoding": "mp3",
"compression_rate": 1,
"rate": 24000,
"speed_ratio": 0.8, // 语速
"volume_ratio": 1.0,
"pitch_ratio": 1.0,
"emotion": "happy",
"language": "en" // 这里切换中英文
},
"request": {
"reqid": content.sentenceId,
"text": content.text,
"text_type": "plain",
"operation": "query",
"silence_duration": "125",
"with_frontend": "1",
"frontend_type": "unitTson",
"pure_english_opt": "1"
}
}
return uniCloud.httpclient.request(host, {
method: 'POST',
headers: header,
data: requestBody,
contentType: 'json',
dataType:'text', // 注意这个参数,要以字符串形式返回
timeout: 60000
})
}
//----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
// 云对象调用
async makeVoiceByDouyin(content) {
const voiceRes = await makerDouyinVoice(content)
console.log(voiceRes)
if (voiceRes.status == 200) {
// 火山引擎返回的数据是字符串 要转换成json对象
const voiceData = JSON.parse(voiceRes.data)
if(voiceData.code == 3000){
// 火山引擎返回的音频数据 base64 要转换成 Buffer
const voiceBuffer = Buffer.from(voiceData.data,'base64')
// 上传到云存储
const res = await uniCloud.uploadFile({
cloudPath: `${content.sentenceId}.mp3`,
fileContent: voiceBuffer
})
return res
}
return {
errCode:1,
errMsg:'语音合成失败'
}
}
return {
errCode:1,
errMsg: '合成失败'
}
}
收起阅读 »
关于unicloud账号 短信的等问题
希望官方可以出一个关于查询当前unicloud账号下云短信账号余额,以及模版列表,签名列表,申请签名,申请短信模板,以及充值短信账号条数等的功能的接口
这样就可以在自己的项目的管理后台进行操作,申请短信模板,签名,充值余额啥的,这样方便交给客户之后 不懂技术的客户 好操作
其他模块最好也能出一下相关接口
就像微信开放文档这样
希望官方可以出一个关于查询当前unicloud账号下云短信账号余额,以及模版列表,签名列表,申请签名,申请短信模板,以及充值短信账号条数等的功能的接口
这样就可以在自己的项目的管理后台进行操作,申请短信模板,签名,充值余额啥的,这样方便交给客户之后 不懂技术的客户 好操作
其他模块最好也能出一下相关接口
就像微信开放文档这样
uniCloud使用nodemailer发送邮件|SMTP
环境: 支付宝小程序云.
云对象: emailSend_Time.
目标: 定时调用nodemailer 邮件发送.
实测以下代码可以发送成功.
package.json
{
"name": "emailSend_Time",
"dependencies": {
"nodemailer": "^6.9.12"
},
"extensions": {
"uni-cloud-jql": {}
},
"cloudfunction-config": {
"memorySize": 256,
"triggers": [{
"name": "myEmailTrigger",
"type": "timer",
"config": "0 0 * * * * *"
}]
}
}
emailSend_Time
const nodemailer = require('nodemailer');
const sendEmail = async ({
to,
subject,
html
}) => {
const transporter = nodemailer.createTransport({
host: 'smtp.163.com',
port: 465,
secure: true,
auth: {
user: '邮箱',
pass: '授权码'
},
});
await transporter.sendMail({
from: {
name: 'XXXX',
address: '邮箱'
},
to,
subject,
html
});
}
module.exports = {
_timing: async function() {
await sendEmail({
to: '发送人邮箱',
subject: '幸福通知',
html: `<h1 style="text-align: center;"><span style="font-family: helvetica, arial, sans-serif; font-size: 36pt; color: #e03e2d;">测试</span></h1>`
})
return " "
},
sendEmail: sendEmail
}
环境: 支付宝小程序云.
云对象: emailSend_Time.
目标: 定时调用nodemailer 邮件发送.
实测以下代码可以发送成功.
package.json
{
"name": "emailSend_Time",
"dependencies": {
"nodemailer": "^6.9.12"
},
"extensions": {
"uni-cloud-jql": {}
},
"cloudfunction-config": {
"memorySize": 256,
"triggers": [{
"name": "myEmailTrigger",
"type": "timer",
"config": "0 0 * * * * *"
}]
}
}
emailSend_Time
const nodemailer = require('nodemailer');
const sendEmail = async ({
to,
subject,
html
}) => {
const transporter = nodemailer.createTransport({
host: 'smtp.163.com',
port: 465,
secure: true,
auth: {
user: '邮箱',
pass: '授权码'
},
});
await transporter.sendMail({
from: {
name: 'XXXX',
address: '邮箱'
},
to,
subject,
html
});
}
module.exports = {
_timing: async function() {
await sendEmail({
to: '发送人邮箱',
subject: '幸福通知',
html: `<h1 style="text-align: center;"><span style="font-family: helvetica, arial, sans-serif; font-size: 36pt; color: #e03e2d;">测试</span></h1>`
})
return " "
},
sendEmail: sendEmail
}
收起阅读 »