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

Merge branch 'next' into 'main'

1.0.3

See merge request !1
<script> <script>
import uniIdPagesInit from '@/uni_modules/uni-id-pages/init.js';
import config from '@/config.js';
export default { export default {
onLaunch: function() { onLaunch:async function() {
// 初始化uni身份信息管理模块
uniIdPagesInit();
console.log('App Launch') console.log('App Launch')
let version = uni.getSystemInfoSync().uniRuntimeVersion if(typeof(uniCloud.SSEChannel) == 'undefined' ){
function toNum(a){
const c = a.toString().split('.');
const num_place = ["", "0", "00", "000", "0000"],
r = num_place.reverse();
for (let i = 0; i < c.length; i++){
const len=c[i].length;
c[i]=r[len]+c[i];
}
return c.join('');
}
if(toNum(version) < toNum("3.8.0")){
uni.showModal({ uni.showModal({
content: '示例的HBuilderX版本不得低于3.8.0,请升级', content: '项目,仅支持HBuilderX 正式版 v3.7.10 或 alpha v3.8.0及以上版本请升级',
showCancel: false showCancel: false
}); });
} }
// #ifdef MP-WEIXIN
if(config.adpid){
uniCloud.initSecureNetworkByWeixin()
}
// #endif
}, },
onShow: function() { onShow: function() {
console.log('App Show') console.log('App Show')
......
## 简介
> 支持HBuilder版本,正式版 3.7.10+,alpha版 3.8.0+
`uni-ai-chat`是基于[uni-ai](uni-ai.md)的聊天项目模板。它包含一个前端页面(路径:`/pages/chat/chat.vue`)和一个云对象(路径:`uniCloud/cloudfunctions/uni-ai-chat/index.obj.js`)
它支持上文总结、流式响应,把众多复杂的ai聊天逻辑都封装好了。
**插件下载地址:[https://ext.dcloud.net.cn/plugin?name=uni-ai-chat](https://ext.dcloud.net.cn/plugin?name=uni-ai-chat)**
视频效果:
<video controls src="https://web-assets.dcloud.net.cn/unidoc/zh/uni-ai-chat/uni-ai-stream.mov" style="max-width: 100%; max-height: 70vh;"></video>
## 体验步骤
1. 如之前未使用过uni-app,那请从头学起。[uni-app官网](https://uniapp.dcloud.net.cn)
2. 如果你还没有开通uniCloud,需要登录[https://unicloud.dcloud.net.cn/](https://unicloud.dcloud.net.cn/),创建一个服务空间。
3. 如果你不了解[uni-ai](uni-ai.md),请务必阅读相关文档。
4. 打开本插件`uni-ai-chat`下载地址:[https://ext.dcloud.net.cn/plugin?name=uni-ai-chat](https://ext.dcloud.net.cn/plugin?name=uni-ai-chat)
5. 点击`使用HBuilderX导入示例项目`
6. 对项目根目录uniCloud点右键选择“云服务空间初始化向导”界面按提示部署项目
7. 在uni-app项目点右键,关联之前创建的服务空间。
8. 运行启动。
9. 如果需要stream流式响应,需要在[dev.dcloud.net.cn](https://dev.dcloud.net.cn)的uni-push2中开通你的应用,然后把云函数上传到uniCloud服务空间,并且运行时在HBuilderX控制台选择`连接云端云函数`
## 配置 @config
文件路径 `uniCloud/cloudfunctions/common/uni-config-center/uni-ai-chat/config.json`
目前的配置文件包括是否开启内容安全,即是否检查用户输入和ai返回结果的合规性。开启后,会延缓一定的响应时间,但保证了合规性。
| 字段 | 类型 | 默认值| 描述 |
| :-------- | :-------- | :--- | :-------------------- |
| contentSecurity | Boolean | false | 开启内容安全识别 |
| spentScore | Number | 0 | 对话一次所需消耗的积分数 |
| earnedScore | object | - | 配置积分的获取策略 |
| &nbsp;&nbsp;&#124;-&nbsp;ad | Number | 3 | 观看1次广告可获得的积分数量 |
| &nbsp;&nbsp;&#124;-&nbsp;price | Number | 3 | 支付1元可获得的积分数量(暂未支持) |
| chatCompletionOptions | Object | - | 支持配置:`model`,`tokensToGenerate`(默认值:512),`temperature`,`topP` </br> 详情查看:[https://uniapp.dcloud.net.cn/uniCloud/uni-ai.html#chat-completion](https://uniapp.dcloud.net.cn/uniCloud/uni-ai.html#chat-completion)|
| llm | Object | - | 大语言模型配置,详情查看:[https://uniapp.dcloud.net.cn/uniCloud/uni-ai.html#get-llm-manager](https://uniapp.dcloud.net.cn/uniCloud/uni-ai.html#get-llm-manager) |
这里内置的内容安全是基于`uni-sec-check`插件,它是免费的,但有一些限制条件,请务必阅读该插件的文档:[https://uniapp.dcloud.net.cn/uniCloud/uni-sec-check.html](uni-sec-check.md)
如果你需要使用其他的内容安全检测方案,请自行集成。
注意:如果对ai返回结果进行内容安全检查,会导致stream流式响应失效。
## stream流式响应实现解析
当访问 AI 接口时,如果生成的回复内容过大,响应时间会很长,导致前端用户需要等待很长时间才能收到结果。所以流式响应可以减少前端用户的等待,让ai回复的内容一个字或一段话逐步出现在前端用户的界面上。
`uni-ai-chat`的流式响应,调用的是`uni-ai`的llm API中的[stream参数](uni-ai.md#chat-completion-stream)
而stream参数是基于云函数的[sse通道](sse-channel.md#cloud-deserialize-channel)封装的。
`sse通道`是基于[uni-push2](https://uniapp.dcloud.net.cn/unipush-v2.html)封装的。
所以**使用流式响应必须给应用开通uni-push**
流式响应的流程图
<img width="500px" src="https://web-assets.dcloud.net.cn/unidoc/zh/uni-ai-chat/20230425203311.jpg">
流式响应的使用需客户端先通过 `new uniCloud.SSEChannel()` 创建 SSE 通道,并获得 `channel` 值(详情请参考:[https://uniapp.dcloud.net.cn/uniCloud/sse-channel.html#create-sse-channel](https://uniapp.dcloud.net.cn/uniCloud/sse-channel.html#create-sse-channel))。在客户端向 uniCloud 云对象发起请求时,需要将该 `channel` 值作为参数一同携带;然后 uniCloud 云对象与 uni-ai 建立 流式响应[(stream)](https://uniapp.dcloud.net.cn/uniCloud/uni-ai.html#chat-completion-stream) 通讯,云对象监听 uni-ai 返回的分片数据,在接收到数据后再通过 sse-channel ([反序列化消息通道](https://uniapp.dcloud.net.cn/uniCloud/sse-channel.html#cloud-deserialize-channel))向客户端推送消息。
### 注意事项 @heed
- 使用`sse-channel`需要先[开通uni-push](https://uniapp.dcloud.net.cn/unipush-v2.html#%E7%AC%AC%E4%B8%80%E6%AD%A5-%E5%BC%80%E9%80%9A)
- 目前uni-push2.0不支持本地调试(后续版本会支持),需要在HBuilderX控制台,更改`连接本地云函数``连接云端云函数`
## 营运专题@ad
v1.0.3起提供了商业化能力,与uni-ai对话消耗积分。
积分可通过看[激励视频广告](https://uniapp.dcloud.net.cn/component/ad-rewarded-video.html)获得。
如需要通过支付获取积分,可自行二次开发,接入[uni-pay](uni-pay.md)
**平台差异说明**
|App|H5|微信小程序|支付宝小程序|百度小程序|字节跳动小程序|QQ小程序|快应用|360小程序|快手小程序|京东小程序|
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
|√(3.4.8+)|x|√(3.4.8+)|x|x|x|x|x|x|x|x|
### 流程概述
1. 开通广告;在[uni-ad](https://uniad.dcloud.net.cn/)官网点击菜单`广告设置`在应用列表,找到你的应用点击`开通广告`
2. 创建激励视频广告位;在[应用列表](https://uniad.dcloud.net.cn/list/app)点击`应用详情`再点击`新建广告位`;如下图:广告类型选`激励视频广告`,配置服务器回调,选uni-ai-chat部署的uniCloud服务空间,回调云函数名称选:`reward-video-callback`
<img width="500px" src="https://dcloud-chjh-web.oss-cn-hangzhou.aliyuncs.com/unidoc/zh/uni-ai-chat/uni-ai-chat-create-adpid.jpg">
**微信小程序端注意**:
- 参考:[uniAD微信小程序广告开通指南](https://ask.dcloud.net.cn/article/39928)开通广告后会自动创建广告位
- 广告服务器回调,提前条件:详情查看[https://uniapp.dcloud.net.cn/uni-ad/ad-rewarded-video.html#callbackweixin](https://uniapp.dcloud.net.cn/uni-ad/ad-rewarded-video.html#callbackweixin)
3. 配置广告位唯一标识(adpid)
- 客户端,配置路径:`/config.js`
- 服务端,配置路径:`uniCloud/cloudfunctions/common/uni-config-center/uni-ad/config.json`
4. 一般情况下,新注册的用户将获得少量初始积分(即:对于新注册用户,系统将给予其n次免费对话机会);在`uni-id/hooks`配置。路径:`uniCloud/cloudfunctions/common/uni-config-center/uni-id/hooks/index.js`
5. 配置对话一次需要消耗多少积分`needReduceScore`[详情查看](#config)
6. 配置看一次广告获得多少积分`earnedScore.ad`[详情查看](#config)
注意:因为广告服务器回调走的是云端,以上配置项,配置完成后记得上传。
7. 客户端需要登录才能消耗积分才能与ai对话,所以要将chat页面配置为必须登录的页面。以下是pages.json里的设置
```json
"uniIdRouter": {
"loginPage": "uni_modules/uni-id-pages/pages/login/login-withoutpwd",
"needLogin": [
"pages/chat/chat"
],
"resToLogin": true
}
```
> 更多uniIdRouter自动路由详情参考:[https://uniapp.dcloud.net.cn/uniCloud/uni-id-summary.html#uni-id-router](https://uniapp.dcloud.net.cn/uniCloud/uni-id-summary.html#uni-id-router)
## 二次开发
**主要文件目录结构**
<pre v-pre="" data-lang="">
<code class="lang-" style="padding:0">
uni-ai-chat
├── uniCloud
│  ├── cloudfunctions
│  │  ├── common 公共模块目录
│  │  │  ├── uni-config-center uniCloud 配置中心
│  │  │  │  ├── uni-ad
│  │  │  │  │  └── config.json 广告信息配置
│  │  │  │  ├── uni-ai-chat
│  │  │  │  │  └── config.json ai对话项目配置文件<a target="_blank" href="/#config">详情查看</a>
│  │  │  │  ├── uni-id
│  │  │  │  │  ├── config.json 身份信息配置文件
│  │  │  │  │  └── hooks
│  │  │  │  │   └── index.js 用户注册钩子文件
│  │  │  │  └── uni-open-bridge 三方平台认证凭据管理配置
│  │  │  └── uni-sec-check 内容安全检测公共模块
│  │  ├── reward-video-callback 激励视频广告回调云函数
│  │  ├── uni-ai-chat uni-ai-chat云对象
│  │  └── uni-open-bridge 三方平台认证凭据管理模块云对象
│  └── database
│  ├── newQuery.jql
│  └── uni-ai-chat.schema.json
├── components
│  ├── uni-ad-rewarded-video 激励视频广告组件
│  └── uni-ai-msg 消息内容渲染组件
├── lib 资源文件目录
│  ├── highlight
│  │  ├── github-dark.min.css 代码高亮库样式文件
│  │  └── highlight.min.js 代码高亮库脚本文件
│  ├── html-parser.js HTML String转nodes数组脚本文件
│  └── markdown-it.min.js Markdown 渲染库脚本文件
├── pages 页面目录
│  └── chat 对话页面
├── static 静态资源目录
├─ uni_modules uni-app模块目录
│  ├── uni-id-pages 统一身份信息管理模块<a target="_blank" href="https://ext.dcloud.net.cn/plugin?name=uni-id-pages">详情查看</a>
│  ├── uni-open-bridge 三方平台认证凭据管理模块<a target="_blank" href="https://ext.dcloud.net.cn/plugin?name=uni-id-pages">详情查看</a>
│  ├── uni-id-pages 统一身份信息管理模块<a target="_blank" href="https://ext.dcloud.net.cn/plugin?name=uni-id-pages">详情查看</a>
│  └── uni-sec-check 内容安全检测模块<a target="_blank" href="https://ext.dcloud.net.cn/plugin?name=uni-sec-check">详情查看</a>
├─ App.vue 应用配置,用来配置App全局样式以及监听 <a href="/collocation/frame/lifecycle?id=应用生命周期">应用生命周期</a>
├─ changelog.md 项目更新日志
├─ config.js 项目配置文件
├─ main.js 项目初始化入口文件
├─ manifest.json 配置应用名称、appid、logo、版本等打包信息,<a href="/collocation/manifest">详情参考</a>
├─ pages.json 配置页面路由、导航条、选项卡等页面类信息,<a href="/collocation/pages">详情参考</a>
└─ redme.md 自述文件
</code>
</pre>
> 完整的uni-app目录结构[详情参考](https://uniapp.dcloud.io/frame?id=%e7%9b%ae%e5%bd%95%e7%bb%93%e6%9e%84)
### 实现通过支付获取积分
二开流程思路如下:
- 服务端
根据[uni-pay](uni-pay.md)文档集成支付功能,在支付成功异步回调(路径:`uni-pay-co/notify/earnedScore.js`
给相关用户加积分示例代码如下:
```js
'use strict';
// 读取配置的“支付1元可获得的积分数量”值
const uniAiChatConfig = require('uni-config-center')({
pluginId: 'uni-ai-chat'
}).config()
module.exports = async (obj) => {
let user_order_success = true;
let { data = {} } = obj;
let {
user_id, //对应用户唯一身份标识
total_fee, // 支付的金额
order_no,
out_trade_no
} = data; // uni-pay-orders 表内的数据均可获取到
let score = db.command.inc(uniAiChatConfig.earnedScore.pay * total_fee/100)
let res = await db.collection('uni-id-users')
.doc(user_id)
.update({score})
// console.log(res)
return user_order_success;
};
```
- 客户端
1. 新建组件`earned-score-pay`实现支付并回调支付结果
2. 在chat页面(路径:`pages/chat/chat.vue`)搜索 `uni-ad-rewarded-video`修改同位置代码块如下:
```html
<view v-if="index == msgList.length-1 && msg.insufficientScore">
<uni-ad-rewarded-video v-if="adpid" :adpid="adpid" @onAdClose="onAdClose"></uni-ad-rewarded-video>
<earned-score-pay @success="paySuccess"></earned-score-pay>
</view>
```
```js
paySuccess(){
this.msgList.pop()
this.$nextTick(()=>{
this.retriesSendMsg()
uni.showToast({
title: '积分余额:'+score,
icon: 'none'
});
})
}
```
## 常见错误
**uni-ai相关错误请参考:**[uni-ai错误码](uni-ai.md#err-code)
**错误信息:"certificate has expired"**
请参考文档:[云函数通过https访问其他服务器时出现“certificate has expired”](faq.md#lets-encrypt-cert)
## 其他
DCloud基于`uni-ai`提供了很多开源模板,除了本项目`uni-ai-chat`,还有:
- [uni-cms](uni-cms.md),一款集成了ai生成文章内容的开源内容管理系统。
## 交流群
更多问题欢迎加入uni-ai官方交流群 QQ群号:[699680439](https://qm.qq.com/cgi-bin/qm/qr?k=P_JoYXY56vNfb78uNHwwzqpODwl9e89B&jump_from=webapi&authKey=GDp321q9ZYW4V0ZQcejXikwMnNRs4KVBcQXMADs8lvC0hifSH9ORHsyERy6vO4bA)
## 1.0.3(2023-05-09)
- 新增 提供了商业化能力,与uni-ai对话消耗积分。积分可通过看[激励视频广告](https://uniapp.dcloud.net.cn/component/ad-rewarded-video.html)获得。
- 优化 根据配置的llm服务商以及是否开启流式响应,自动处理是否逐字返回
- 修复 llm服务商为openai时,内容换行位置不正确的问题
- 修复 消息发出后uni-ai回复未完成时,点击清空后没有终止会话,引起卡在ai正在回复中的问题
- 修复 流式响应模式 uni-ai回复的代码块不完整时 内容会出现多余的字符(<span class="cursor">|</span>)的问题
- 修复 服务端超时等错误时,客户端卡在ai正在回复中的问题
- 优化 网络错误等因素导致发送失败后,点击重试按钮 消息状态图标没有立即变成发送中的问题
- 优化 自动过滤历史消息中的涉敏内容,防止因上文涉敏而造成后续对话都涉敏的问题
- 修复 消息发出后,uni-ai未答复时直接刷新界面 会卡住的问题
## 1.0.2(2023-04-26)
- 修改错误的HBuilderX版本要求的提示
## 1.0.1(2023-04-25) ## 1.0.1(2023-04-25)
- 新增内容安全识别 - 新增内容安全识别
- 不再提供stream设置ui,自动根据是否开通并启用uni-push设置 - 不再提供stream设置ui,自动根据是否开通并启用uni-push设置
## 1.0.0(2023-04-24) ## 1.0.0(2023-04-24)
1.0.0 1.0.0
\ No newline at end of file
<template>
<view>
<ad-rewarded-video ref="rewardedVideo" :adpid="adpid" :preload="false" :disabled="true" :loadnext="true"
:url-callback="urlCallback" @load="onAdLoad" @close="onAdClose" @error="onAdError"
v-slot:default="{ loading, error }">
<text v-if="error" class="text">广告加载失败</text>
</ad-rewarded-video>
<button class="text" @click="callAd" >去看广告</button>
</view>
</template>
<script>
export default {
name: "uni-ad-rewarded-video",
data() {
return {}
},
props: {
adpid: {
type: String,
default(){
return '1053355918'
}
},
},
computed: {
// 回调URL
urlCallback() {
let uid = uniCloud.getCurrentUserInfo().uid
return {
userId: uid,
extra: JSON.stringify({
user_id: uid,
unique_id: uid + Date.now(),
unique_type: "uni-ai-chat"
})
}
},
},
async mounted() {
},
methods: {
callAd() {
// #ifdef H5
// 如果在浏览器中,则提示需在App或小程序中操作
return uni.showModal({
content: '浏览器不支持广告播放, 请在App或小程序中操作',
showCancel: false,
confirmText:"知道了"
})
// #endif
// 如果用户状态无效,则提示需要登录
if (uniCloud.getCurrentUserInfo().tokenExpired < Date.now()) {
uni.showModal({
content: '请登录后操作',
success: ({
confirm
}) => {
if(confirm){
// 登录跳转URL 请根据实际情况修改
// 获取当前页面信息
let pages = getCurrentPages()
let currentPage = pages[pages.length - 1]
let url = '/uni_modules/uni-id-pages/pages/login/login-withoutpwd' +
(currentPage.route ? ('?uniIdRedirectUrl=' + currentPage.route) : '')
uni.navigateTo({
url,
complete(e) {
console.log(e);
}
});
}
}
})
return
}
// 显示广告
this.$refs.rewardedVideo.show()
},
onAdLoad(e) {
console.log('onAdLoad', e)
},
onAdClose(e) {
console.log('onAdClose', e)
this.$emit('onAdClose',e)
},
onAdError(e) {
console.log('onAdError', e)
},
}
}
</script>
<style lang="scss">
</style>
\ No newline at end of file
@import "@/lib/highlight/github-dark.min.css";
/* #ifndef APP-NVUE */
.rich-text-box ::v-deep pre.hljs {
padding: 5px 8px;
margin: 5px 0;
overflow: auto;
}
.cursor {
display: none;
}
.show-cursor .cursor {
display: inline-block;
color: blue;
font-weight: bold;
animation: blinking 1s infinite;
}
@keyframes blinking {
from {
opacity: 1.0;
}
to {
opacity: 0.0;
}
}
/* #endif */
/* #ifdef H5 */
.copy-box {
position: fixed;
}
// .copy-mask{
// background-color: rgba(255,255,255,0.5);
// width: 100vw;
// height: 100vh;
// position: fixed;
// top: 0;
// left: 0;
// z-index: 9;
// }
.copy {
position: fixed;
background-color: #fff;
box-shadow: 0 0 3px #aaa;
padding: 5px;
border-radius: 5px;
z-index: 999;
cursor: pointer;
font-size: 14px;
color: #222;
}
.copy:hover {
color: #00a953;
}
/* #endif */
\ No newline at end of file
<template> <template>
<view class="rich-text-box" :class="{'show-cursor':showCursor}" ref="rich-text-box"> <view class="rich-text-box" :class="{'show-cursor':showCursor}" ref="rich-text-box">
<rich-text v-if="nodes&&nodes.length" space="nbsp" :nodes="nodes"></rich-text> <rich-text v-if="nodes&&nodes.length" space="nbsp" :nodes="nodes"></rich-text>
<!-- #ifdef H5 -->
<view class="copy-box" :style="{left,top}"> <!-- #ifdef H5 -->
<text class="copy" @click="copy">复制</text> <view class="copy-box" :style="{left,top}">
<!-- <view v-if="left != '-100px'" class="copy-mask" @click="left = '-100px'"></view> --> <text class="copy" @click="copy">复制</text>
</view> <!-- <view v-if="left != '-100px'" class="copy-mask" @click="left = '-100px'"></view> -->
<!-- #endif --> </view>
<!-- #endif -->
</view> </view>
</template> </template>
<script> <script>
import parseHtml from './html-parser.js'; import MarkdownIt from '@/lib/markdown-it.min.js';
import MarkdownIt from "markdown-it";
import hljs from 'highlight.js'; // hljs是由 Highlight.js 经兼容性修改后的文件,请勿直接升级。否则会造成uni-app-vue3-Android下有兼容问题
import hljs from "@/lib/highlight/highlight-uni.min.js";
import parseHtml from '@/lib/html-parser.js';
// console.log('hljs--',hljs);
// console.log('hljs--',hljs.getLanguage('js'));
const md = new MarkdownIt({ const markdownIt = MarkdownIt({
html: true, html: true,
highlight: function(str, lang) { highlight: function(str, lang) {
if (lang && hljs.getLanguage(lang)) { // if (lang && hljs.getLanguage(lang)) {
try { // console.error('lang', lang)
return '<pre class="hljs" style="padding: 5px 8px;margin: 5px 0;overflow: auto;"><code>' + // try {
hljs.highlight(lang, str, true).value + // return '<pre class="hljs" style="padding: 5px 8px;margin: 5px 0;overflow: auto;"><code>' +
'</code></pre>'; // hljs.highlight('lang', str, true).value +
} catch (__) {} // '</code></pre>';
} // } catch (__) {}
// }
try {
return '<pre class="hljs" style="padding: 5px 8px;margin: 5px 0;overflow: auto;"><code>' +
hljs.highlightAuto(str).value +
'</code></pre>';
} catch (__) {}
return '<pre class="hljs" style="padding: 5px 8px;margin: 5px 0;overflow: auto;"><code>' + md.utils.escapeHtml(str) + '</code></pre>'; return '<pre class="hljs" style="padding: 5px 8px;margin: 5px 0;overflow: auto;"><code>' +
markdownIt.utils.escapeHtml(str) + '</code></pre>';
} }
}) })
export default { export default {
name: "msg", name: "msg",
data() { data() {
return { return {
left:"-100px", left: "-100px",
top:"-100px" top: "-100px"
}; };
}, },
mounted() { mounted() {
// #ifdef H5 // #ifdef H5
// web端限制不选中文字时出现系统右键菜单 // web端限制不选中文字时出现系统右键菜单
let richTextBox = this.$refs['rich-text-box'] let richTextBox = this.$refs['rich-text-box']
if (richTextBox) { if (richTextBox) {
richTextBox.$el.addEventListener('contextmenu', (e) => { richTextBox.$el.addEventListener('contextmenu', (e) => {
if (!document.getSelection().toString()) { if (!document.getSelection().toString()) {
console.log(e); // console.log(e);
this.top = e.y+'px' this.top = e.y + 'px'
this.left = e.x+'px' this.left = e.x + 'px'
console.log(e.x); // console.log(e.x);
console.log(e.y); // console.log(e.y);
e.preventDefault() e.preventDefault()
} }
}) })
} }
document.addEventListener('click',()=>{ document.addEventListener('click', () => {
this.left = "-100px" this.left = "-100px"
}) })
// #endif // #endif
}, },
props: { props: {
md: { md: {
...@@ -76,86 +92,35 @@ ...@@ -76,86 +92,35 @@
} }
}, },
computed: { computed: {
html() { html() {
let html = md.render(this.md + '<span class="cursor">|</span>') if(this.md.split("```").length%2){
return html return markdownIt.render(this.md + ' \n <span class="cursor">|</span>');
}else{
return markdownIt.render(this.md) + ' \n <span class="cursor">|</span>';
}
}, },
nodes() { nodes() {
// return this.html
// console.log('parseHtml(this.html)',parseHtml(this.html));
return parseHtml(this.html) return parseHtml(this.html)
} }
}, },
methods:{ methods: {
// #ifdef H5 // #ifdef H5
copy(){ copy() {
uni.setClipboardData({ uni.setClipboardData({
data:this.md, data: this.md,
showToast:false, showToast: false,
}) })
this.left = "-100px" this.left = "-100px"
} }
// #endif // #endif
} }
} }
</script> </script>
<style lang="scss"> <style lang="scss">
@import "highlight.js/styles/agate.css"; /* #ifndef VUE3 && APP-PLUS */
@import "highlight.js/styles/a11y-dark.css"; @import "@/components/uni-ai-msg/uni-ai-msg.scss";
/* #endif */
/* #ifndef APP-NVUE */
.rich-text-box ::v-deep pre.hljs{
padding: 5px 8px;
margin: 5px 0;
overflow: auto;
}
.cursor{
display: none;
}
.show-cursor .cursor {
display: inline-block;
color: blue;
font-weight: bold;
animation: blinking 1s infinite;
}
@keyframes blinking {
from {
opacity: 1.0;
}
to {
opacity: 0.0;
}
}
/* #endif */
/* #ifdef H5 */
.copy-box{
position: fixed;
}
// .copy-mask{
// background-color: rgba(255,255,255,0.5);
// width: 100vw;
// height: 100vh;
// position: fixed;
// top: 0;
// left: 0;
// z-index: 9;
// }
.copy{
position: fixed;
background-color: #fff;
box-shadow: 0 0 3px #aaa;
padding: 5px;
border-radius: 5px;
z-index: 999;
cursor: pointer;
font-size: 14px;
color: #222;
}
.copy:hover{
color: #00a953;
}
/* #endif */
</style> </style>
\ No newline at end of file
export default {
adpid:false
}
\ No newline at end of file
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
Theme: GitHub Dark
Description: Dark theme as seen on github.com
Author: github.com
Maintainer: @Hirse
Updated: 2021-05-15
Outdated base version: https://github.com/primer/github-syntax-dark
Current colors taken from GitHub's CSS
*/.hljs{color:#c9d1d9;background:#0d1117}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#ff7b72}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#d2a8ff}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable{color:#79c0ff}.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#a5d6ff}.hljs-built_in,.hljs-symbol{color:#ffa657}.hljs-code,.hljs-comment,.hljs-formula{color:#8b949e}.hljs-name,.hljs-quote,.hljs-selector-pseudo,.hljs-selector-tag{color:#7ee787}.hljs-subst{color:#c9d1d9}.hljs-section{color:#1f6feb;font-weight:700}.hljs-bullet{color:#f2cc60}.hljs-emphasis{color:#c9d1d9;font-style:italic}.hljs-strong{color:#c9d1d9;font-weight:700}.hljs-addition{color:#aff5b4;background-color:#033a16}.hljs-deletion{color:#ffdcd7;background-color:#67060c}
\ No newline at end of file
此差异已折叠。
此差异已折叠。
{ {
"name" : "uni-ai-chat", "name" : "uni-ai-chat",
"appid" : "__UNI__8F14B14", "appid" : "__UNI__HelloUniApp",
"description" : "", "description" : "",
"versionName" : "1.0.0", "versionName" : "1.0.0",
"versionCode" : "100", "versionCode" : "100",
...@@ -15,7 +15,9 @@ ...@@ -15,7 +15,9 @@
"autoclose" : true, "autoclose" : true,
"delay" : 0 "delay" : 0
}, },
"modules" : {}, "modules" : {
"OAuth" : {}
},
"distribute" : { "distribute" : {
"android" : { "android" : {
"permissions" : [ "permissions" : [
...@@ -36,17 +38,41 @@ ...@@ -36,17 +38,41 @@
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>" "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
] ]
}, },
"ios" : {}, "ios" : {
"sdkConfigs" : {} "dSYMs" : false
},
"sdkConfigs" : {
"ad" : {
"gdt" : {},
"ks" : {},
"ks-content" : {},
"sigmob" : {},
"hw" : {},
"bd" : {},
"gm" : {},
"wm" : {}
},
"oauth" : {
"univerify" : {}
},
"push" : {}
}
} }
}, },
"quickapp" : {}, "quickapp" : {},
"mp-weixin" : { "mp-weixin" : {
"appid" : "", "appid" : "wxdcbd74f0961c068d",
"setting" : { "setting" : {
"urlCheck" : false "urlCheck" : false,
"es6" : false
}, },
"usingComponents" : true "usingComponents" : true,
"secureNetwork" : {
"enable" : false
},
"unipush" : {
"enable" : false
}
}, },
"mp-alipay" : { "mp-alipay" : {
"usingComponents" : true "usingComponents" : true
...@@ -60,10 +86,14 @@ ...@@ -60,10 +86,14 @@
"uniStatistics" : { "uniStatistics" : {
"enable" : false "enable" : false
}, },
"vueVersion" : "3", "vueVersion" : "2",
"h5" : { "h5" : {
"unipush" : { "unipush" : {
"enable" : true "enable" : false
},
"devServer" : {
"port" : 8080
} }
} },
"fallbackLocale" : "zh-Hans"
} }
{
"name": "uni-ai-chat",
"version": "1.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "uni-ai-chat",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"highlight.js": "^11.7.0",
"markdown-it": "^13.0.1"
}
},
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
},
"node_modules/entities": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/entities/-/entities-3.0.1.tgz",
"integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==",
"engines": {
"node": ">=0.12"
}
},
"node_modules/highlight.js": {
"version": "11.7.0",
"resolved": "https://registry.npmmirror.com/highlight.js/-/highlight.js-11.7.0.tgz",
"integrity": "sha512-1rRqesRFhMO/PRF+G86evnyJkCgaZFOI+Z6kdj15TA18funfoqJXvgPCLSf0SWq3SRfg1j3HlDs8o4s3EGq1oQ==",
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/linkify-it": {
"version": "4.0.1",
"resolved": "https://registry.npmmirror.com/linkify-it/-/linkify-it-4.0.1.tgz",
"integrity": "sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==",
"dependencies": {
"uc.micro": "^1.0.1"
}
},
"node_modules/markdown-it": {
"version": "13.0.1",
"resolved": "https://registry.npmmirror.com/markdown-it/-/markdown-it-13.0.1.tgz",
"integrity": "sha512-lTlxriVoy2criHP0JKRhO2VDG9c2ypWCsT237eDiLqi09rmbKoUetyGHq2uOIRoRS//kfoJckS0eUzzkDR+k2Q==",
"dependencies": {
"argparse": "^2.0.1",
"entities": "~3.0.1",
"linkify-it": "^4.0.1",
"mdurl": "^1.0.1",
"uc.micro": "^1.0.5"
},
"bin": {
"markdown-it": "bin/markdown-it.js"
}
},
"node_modules/mdurl": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/mdurl/-/mdurl-1.0.1.tgz",
"integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g=="
},
"node_modules/uc.micro": {
"version": "1.0.6",
"resolved": "https://registry.npmmirror.com/uc.micro/-/uc.micro-1.0.6.tgz",
"integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA=="
}
},
"dependencies": {
"argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
},
"entities": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/entities/-/entities-3.0.1.tgz",
"integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q=="
},
"highlight.js": {
"version": "11.7.0",
"resolved": "https://registry.npmmirror.com/highlight.js/-/highlight.js-11.7.0.tgz",
"integrity": "sha512-1rRqesRFhMO/PRF+G86evnyJkCgaZFOI+Z6kdj15TA18funfoqJXvgPCLSf0SWq3SRfg1j3HlDs8o4s3EGq1oQ=="
},
"linkify-it": {
"version": "4.0.1",
"resolved": "https://registry.npmmirror.com/linkify-it/-/linkify-it-4.0.1.tgz",
"integrity": "sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==",
"requires": {
"uc.micro": "^1.0.1"
}
},
"markdown-it": {
"version": "13.0.1",
"resolved": "https://registry.npmmirror.com/markdown-it/-/markdown-it-13.0.1.tgz",
"integrity": "sha512-lTlxriVoy2criHP0JKRhO2VDG9c2ypWCsT237eDiLqi09rmbKoUetyGHq2uOIRoRS//kfoJckS0eUzzkDR+k2Q==",
"requires": {
"argparse": "^2.0.1",
"entities": "~3.0.1",
"linkify-it": "^4.0.1",
"mdurl": "^1.0.1",
"uc.micro": "^1.0.5"
}
},
"mdurl": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/mdurl/-/mdurl-1.0.1.tgz",
"integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g=="
},
"uc.micro": {
"version": "1.0.6",
"resolved": "https://registry.npmmirror.com/uc.micro/-/uc.micro-1.0.6.tgz",
"integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA=="
}
}
}
{ {
"id": "uni-ai-chat", "id": "uni-ai-chat",
"name": "uni-ai-chat", "name": "uni-ai-chat",
"version": "1.0.1", "version": "1.0.3",
"description": "基于uni-ai的聊天示例项目,支持流式、支持前文总结,云端一体", "description": "基于uni-ai的聊天示例项目,支持流式、支持前文总结,云端一体",
"main": "main.js", "main": "main.js",
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1"
}, },
"repository": "https://github.com/dcloudio/uni-ui", "repository": "https://gitcode.net/dcloud/uni-ai-chat",
"keywords": [ "keywords": [
"uni-ai-chat" "uni-ai-chat"
], ],
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {},
"highlight.js": "^11.7.0", "displayName": "uni-ai-chat",
"markdown-it": "^13.0.1" "dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
}
}, },
"displayName": "uni-ai-chat", "engines": {
"dcloudext": { "HBuilderX": "^3.7.10"
"sale": { },
"regular": { "directories": {
"price": "0.00" "example": "../../temps/example_temps"
}, },
"sourcecode": { "uni_modules": {
"price": "0.00" "encrypt": [],
} "platforms": {
}, "cloud": {
"contact": { "tcb": "y",
"qq": "" "aliyun": "y"
}, },
"declaration": { "client": {
"ads": "无", "App": {
"data": "无", "app-vue": "y",
"permissions": "无" "app-nvue": "n"
}, },
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui", "H5-mobile": {
"type": "unicloud-template-project" "Safari": "y",
}, "Android Browser": "y",
"repository": "https://github.com/dcloudio/uni-ui", "微信浏览器(Android)": "y",
"engines": { "QQ浏览器(Android)": "y"
"HBuilderX": "" },
}, "H5-pc": {
"directories": { "Chrome": "y",
"example": "../../temps/example_temps" "IE": "y",
}, "Edge": "y",
"dcloudext": { "Firefox": "y",
"category": [ "Safari": "y"
"前端组件", },
"通用组件" "小程序": {
], "微信": "y",
"sale": { "阿里": "u",
"regular": { "百度": "u",
"price": "0.00" "字节跳动": "u",
}, "QQ": "u"
"sourcecode": { },
"price": "0.00" "快应用": {
} "华为": "u",
}, "联盟": "u"
"contact": { },
"qq": "" "Vue": {
}, "vue2": "y",
"declaration": { "vue3": "y"
"ads": "无", }
"data": "无", }
"permissions": "无" }
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
},
"uni_modules": {
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "n"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "u",
"百度": "u",
"字节跳动": "u",
"QQ": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
} }
} }
\ No newline at end of file
{ {
"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages "pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
{ {
"path" : "pages/chat/chat", "path": "pages/chat/chat",
"style" : "style": {
{ "navigationBarTitleText": "uni-ai-chat",
"navigationBarTitleText": "uni-ai-chat", "enablePullDownRefresh": false
"enablePullDownRefresh": false }
} }, {
"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": {
"enablePullDownRefresh": false,
"navigationBarTitleText": ""
}
}, {
"path": "uni_modules/uni-id-pages/pages/userinfo/change_pwd/change_pwd",
"style": {
"enablePullDownRefresh": false,
"navigationBarTitleText": "修改密码"
}
}, {
"path": "uni_modules/uni-id-pages/pages/register/register-admin",
"style": {
"enablePullDownRefresh": false,
"navigationBarTitleText": "注册管理员账号"
}
}, {
"path": "uni_modules/uni-id-pages/pages/userinfo/set-pwd/set-pwd",
"style": {
"enablePullDownRefresh": false,
"navigationBarTitleText": "设置密码"
}
}, {
"path": "uni_modules/uni-id-pages/pages/userinfo/realname-verify/realname-verify",
"style": {
"enablePullDownRefresh": false,
"navigationBarTitleText": "实名认证"
}
} }
], ],
"globalStyle": { "globalStyle": {
"navigationBarTextStyle": "black", "navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-app", "navigationBarTitleText": "uni-app",
"navigationBarBackgroundColor": "#F8F8F8", "navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8" "backgroundColor": "#F8F8F8"
}, }
"uniIdRouter": {} // ,"uniIdRouter": {
} // "loginPage": "uni_modules/uni-id-pages/pages/login/login-withoutpwd",
// "needLogin": [
// "pages/chat/chat"
// ],
// "resToLogin": true
// }
}
\ No newline at end of file
此差异已折叠。
'use strict';
const uniADConfig = require('uni-config-center')({
pluginId: 'uni-ad'
}).config()
// 如果 uni-ad 配置项不存在,则抛出错误引导用户配置参数
if (!uniADConfig){
throw new Error('请先完成uni-ad配置')
}
// 自定义业务逻辑方法(在通过签名验证后被执行)
async function nextFn(data) {
console.log('广告回调成功:',JSON.stringify(data));
try{
data.extra = JSON.parse(data.extra)
}catch(e){
console.log('extra不是JSON')
}
if(typeof data.extra == 'object' && data.extra.unique_type == "uni-ai-chat"){
await uniAiChatEarnedScoreByAd(data)
}
// uni-ai-chat 看广告奖励积分
async function uniAiChatEarnedScoreByAd(data){
const uniAiChatConfig = require('uni-config-center')({
pluginId: 'uni-ai-chat'
}).config()
console.log('uniAiChatConfig',uniAiChatConfig);
if(!uniAiChatConfig || !uniAiChatConfig.ad || !uniAiChatConfig.earnedScore.ad){
throw new Error('请先完成uni-ai-chat的广告奖励配置')
}
if(!data.extra.user_id){
return console.error('userId 不能为空');
}
let score = db.command.inc(uniAiChatConfig.earnedScore.ad)
let res = await db.collection('uni-id-users').doc(data.extra.user_id).update({score})
console.log('update res',res)
}
//如果不返回,广点通会2次调用本云函数
return {
"isValid": true
}
}
const crypto = require('crypto');
const db = uniCloud.database();
exports.main = async (event, context) => {
// event = {
// "adpid": "1053355918",
// "platform": "weixin-mp",
// "provider": "sh",
// "trans_id": "6a51e0edb22aa854c394acb104cad761",
// "sign": "7c4a0f7952be42b7578179a10f191b408f3afb49f230ce7558ae483fa761a799",
// "user_id": "6448f5e70c801ca8782c7a6e",
// "extra": "{\"user_id\":\"6448f5e70c801ca8782c7a6e\",\"unique_id\":\"6448f5e70c801ca8782c7a6e1682587255500\",\"unique_type\":\"uni-ai-chat\"}"
// }
console.log('event : ', event);
const trans_id = event.trans_id;
//去uni-config-center通过adpid获取secret
const secret = uniADConfig[event.adpid]
if (!secret){
throw new Error(`未配置,广告位adpid:${adpid}的secret。在uni-AD Web控制台,找到广告位,点击配置激励视频,展开当前广告位项,可看到生成的 Security key`)
}
// 验签请求来源
const sign2 = crypto.createHash('sha256').update(`${secret}:${trans_id}`).digest('hex');
if (event.sign !== sign2) {
console.log('验签失败');
return null;
}
try{
// 存储广告回调日志
const data2 = Object.assign(event, {
ad_type: 0,
create_date: new Date()
})
await db.collection('ad-callback-log').add(data2);
}catch(err){
// 如果选择了腾讯云,需要手动预创建表
console.error(err);
throw new Error('广告回调日志存储失败,请确认:是否已创建ad-callback-log表。')
}
// 已完成验签,可以执行自定义业务逻辑
return await nextFn(event)
};
\ No newline at end of file
{
"name": "reward-video-callback",
"dependencies": {
"uni-config-center": "file:../../../uni_modules/uni-config-center/uniCloud/cloudfunctions/common/uni-config-center"
},
"extensions": {
"uni-cloud-jql": {}
}
}
\ No newline at end of file
...@@ -5,10 +5,13 @@ const createConfig = safeRequire('uni-config-center') ...@@ -5,10 +5,13 @@ const createConfig = safeRequire('uni-config-center')
const config = createConfig({ const config = createConfig({
pluginId: 'uni-ai-chat' pluginId: 'uni-ai-chat'
}).config() }).config()
const db = uniCloud.database();
const userscollection = db.collection('uni-id-users')
const uniIdCommon = require('uni-id-common')
module.exports = { module.exports = {
_before:async function() { _before:async function() {
// 这里是云函数的前置方法,你可以在这里加入你需要逻辑,比如:拦截用户必须登录才能访问等 // 这里是云函数的前置方法,你可以在这里加入你需要逻辑,比如:
/* /*
例如:使用uni-id-pages(链接地址:https://ext.dcloud.net.cn/plugin?id=8577)搭建账户体系。 例如:使用uni-id-pages(链接地址:https://ext.dcloud.net.cn/plugin?id=8577)搭建账户体系。
然后再使用uni-id-common的uniIdCommon.checkToken判断用户端身份,验证不通过你可以直接`throw new Error(“token无效”)`抛出异常拦截访问。 然后再使用uni-id-common的uniIdCommon.checkToken判断用户端身份,验证不通过你可以直接`throw new Error(“token无效”)`抛出异常拦截访问。
...@@ -16,60 +19,123 @@ module.exports = { ...@@ -16,60 +19,123 @@ module.exports = {
或者看一个激励视频广告(详情:https://uniapp.dcloud.net.cn/uni-ad/ad-rewarded-video.html)后才能继续使用 或者看一个激励视频广告(详情:https://uniapp.dcloud.net.cn/uni-ad/ad-rewarded-video.html)后才能继续使用
*** 激励视频是造富神器。行业经常出现几个人的团队,月收入百万的奇迹。 *** *** 激励视频是造富神器。行业经常出现几个人的团队,月收入百万的奇迹。 ***
*/ */
// 从配置中心获取内容安全配置 if(this.getMethodName() == 'send'){
if (this.getMethodName() == 'send' && config.contentSecurity) { // 从配置中心获取是否需要销毁积分
const UniSecCheck = safeRequire('uni-sec-check') if(config.spentScore){
const uniSecCheck = new UniSecCheck({
provider: 'mp-weixin', /*先校验token(用户身份令牌)是否有效,并获得用户的_id*/
requestId: this.getUniCloudRequestId() // 获取客户端信息
}) this.clientInfo = this.getClientInfo()
this.textSecCheck = async (content)=>{ // console.log(this.clientInfo);
let {SSEChannel} = this.getParams()[0]||{}
if(SSEChannel){ // 定义uni-id公共模块对象
return console.log('提示:流式响应模式,内容安全识别功能无效'); this.uniIdCommon = uniIdCommon.createInstance({
clientInfo: this.clientInfo
})
let res = await this.uniIdCommon.checkToken(this.clientInfo.uniIdToken)
if (res.errCode) {
// 如果token校验出错,则抛出错误
throw res
}else{
// 通过token校验则,拿去用户id
this.current_uid = res.uid
}
/* 判断剩余多少积分:拒绝对话、扣除配置的积分数 */
let {data:[{score}]} = await userscollection.doc(this.current_uid).field({'score':1}).get()
console.log('score----',score);
if(score == 0 || score < 0){ //并发的情况下可能花超过
throw "insufficientScore"
} }
// 检测文本 await userscollection.doc(this.current_uid)
const checkRes = await uniSecCheck.textSecCheck({ .update({
content, score:db.command.inc(-1 * config.spentScore)
// openid, })
scene:4, }
version:1 //后续:支持微信登录后,微信小程序端 改用模式2 详情:https://uniapp.dcloud.net.cn/uniCloud/uni-sec-check.html#%E4%BD%BF%E7%94%A8%E5%89%8D%E5%BF%85%E7%9C%8B
// 从配置中心获取内容安全配置
console.log('config.contentSecurity',config.contentSecurity);
if (config.contentSecurity) {
const UniSecCheck = safeRequire('uni-sec-check')
const uniSecCheck = new UniSecCheck({
provider: 'mp-weixin',
requestId: this.getUniCloudRequestId()
}) })
console.log('checkRes检测文本',checkRes); this.textSecCheck = async (content)=>{
if (checkRes.errCode === uniSecCheck.ErrorCode.RISK_CONTENT) { let {sseChannel} = this.getParams()[0]||{}
throw { if(sseChannel){
isSecCheck:true, throw {
errCode: checkRes.errCode, errSubject: 'uni-ai-chat',
errMsg: '文字存在风险', errCode: "sec-check",
result: checkRes.result errMsg: "流式响应模式,内容安全识别功能无效"
}
} }
} else if (checkRes.errCode) { // 检测文本
console.log(`其他原因导致此文件未完成自动审核(错误码:${checkRes.errCode},错误信息:${checkRes.errMsg}),需要人工审核`); const checkRes = await uniSecCheck.textSecCheck({
throw { content,
isSecCheck:true, // openid,
errCode: checkRes.errCode, scene:4,
errMsg: checkRes.errMsg, version:1 //后续:支持微信登录后,微信小程序端 改用模式2 详情:https://uniapp.dcloud.net.cn/uniCloud/uni-sec-check.html#%E4%BD%BF%E7%94%A8%E5%89%8D%E5%BF%85%E7%9C%8B
result: checkRes.result })
console.log('checkRes检测文本',checkRes);
if (checkRes.errCode === uniSecCheck.ErrorCode.RISK_CONTENT) {
console.error({
errCode: checkRes.errCode,
errMsg: '文字存在风险',
result: checkRes.result
});
throw "uni-sec-check:illegalData"
} else if (checkRes.errCode) {
console.log(`其他原因导致此文件未完成自动审核(错误码:${checkRes.errCode},错误信息:${checkRes.errMsg}),需要人工审核`);
console.error({
errCode: checkRes.errCode,
errMsg: checkRes.errMsg,
result: checkRes.result
});
throw "uni-sec-check:illegalData"
} }
} }
let {messages} = this.getParams()[0]||{"messages":[]}
let contentString = messages.map(i=>i.content).join(' ')
console.log('contentString',contentString);
await this.textSecCheck(contentString)
} }
}
let {messages} = this.getParams()[0]||{"messages":[]}
let contentString = messages.map(i=>i.content).join(' ')
console.log('contentString',contentString);
await this.textSecCheck(contentString)
}
}, },
async _after(error, result) { async _after(error, result) {
console.log('_after',{error,result});
if(error){ if(error){
if(error.isSecCheck ) { if(error.errCode && error.errMsg) {
// 符合响应体规范的错误,直接返回
return error
}else if(error == "uni-sec-check:illegalData" ) {
return { return {
"data": { "data": {
"reply": "内容涉及敏感" "reply": "内容涉及敏感",
"illegal":true
}, },
"errCode": 0 "errCode": 0
} }
}else if(error == 'insufficientScore'){
let reply = "积分不足,请看完激励视频广告后再试"
let {sseChannel} = this.getParams()[0]||{}
if(sseChannel){
const channel = uniCloud.deserializeSSEChannel(sseChannel)
await channel.write(reply)
await channel.end({
"insufficientScore":true
})
}else{
return {
"data": {
reply,
"insufficientScore":true
},
"errCode": 0
}
}
}else{ }else{
throw error // 直接抛出异常 throw error // 直接抛出异常
} }
...@@ -81,7 +147,8 @@ module.exports = { ...@@ -81,7 +147,8 @@ module.exports = {
}catch(e){ }catch(e){
return { return {
"data": { "data": {
"reply": "内容涉及敏感" "reply": "内容涉及敏感",
"illegal":true
}, },
"errCode": 0 "errCode": 0
} }
...@@ -91,7 +158,7 @@ module.exports = { ...@@ -91,7 +158,7 @@ module.exports = {
}, },
async send({ async send({
messages, messages,
SSEChannel sseChannel
}) { }) {
// 初次调试时,可不从客户端获取数据,直接使用下面写死在云函数里的数据 // 初次调试时,可不从客户端获取数据,直接使用下面写死在云函数里的数据
// messages = [{ // messages = [{
...@@ -105,57 +172,46 @@ module.exports = { ...@@ -105,57 +172,46 @@ module.exports = {
throw new Error(res.errMsg) throw new Error(res.errMsg)
} }
// 向uni-ai发送消息 // 向uni-ai发送消息
return await chatCompletion({ let {llm,chatCompletionOptions} = config
messages, //消息内容 return await chatCompletion({
SSEChannel, //sse渠道对象 messages, //消息内容
sseChannel, //sse渠道对象
// 以下参数参考:https://uniapp.dcloud.net.cn/uniCloud/uni-ai.html#get-llm-manager llm
// provider:"minimax",//llm服务商,目前支持openai、baidu、minimax。不指定时由uni-ai自动分配
// apiKey:"",//llm服务商的apiKey,如不填则使用uni-ai的key。如指定openai和baidu则必填
// accessToken:"",//llm服务商的accessToken。目前百度文心一言是必填
// proxy:""//可有效连接openai服务器的、可被uniCloud云函数连接的代理服务器地址。格式为IP或域名,域名不包含http前缀,协议层面仅支持https。配置为openai时必填
}) })
async function chatCompletion({ async function chatCompletion({
messages, messages,
summarize = false, summarize = false,
SSEChannel = false, sseChannel = false,
provider, llm
apiKey, }) {
accessToken, const llmManager = uniCloud.ai.getLLMManager(llm)
proxy let res = await llmManager.chatCompletion({
}) { ...chatCompletionOptions,
const llmManager = uniCloud.ai.getLLMManager({
provider,
apiKey,
accessToken,
proxy
})
let res = await llmManager.chatCompletion({
messages, messages,
tokensToGenerate: 512, stream: sseChannel !== false
stream: SSEChannel !== false
}) })
if (SSEChannel) { if (sseChannel) {
let reply = "" let reply = ""
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const channel = uniCloud.deserializeSSEChannel(SSEChannel) const channel = uniCloud.deserializeSSEChannel(sseChannel)
res.on('message', async (message) => { // 判断如果是open-ai按字返回,否则按行返回
// await channel.write(message) if(llm && llm.provider && llm.provider == "openai"){
// console.log('---message----', message) res.on('message', async (message) => {
}) reply += message
res.on('line', async (line) => { await channel.write(message)
reply += line // console.log('---message----', message)
await channel.write(line) })
// console.log('---line----', line) }else{
}) res.on('line', async (line) => {
await channel.write(reply? ("\n\n " + line) : line)
reply += line
// console.log('---line----', line)
})
}
res.on('end', async () => { res.on('end', async () => {
// console.log('---end----',reply) // console.log('---end----',reply)
messages.push({ messages.push({
"content": reply, "content": reply,
"role": "assistant" "role": "assistant"
...@@ -214,7 +270,7 @@ module.exports = { ...@@ -214,7 +270,7 @@ module.exports = {
messages, messages,
summarize: true, summarize: true,
stream: false, stream: false,
SSEChannel: false sseChannel: false
}) })
return res.reply return res.reply
} }
......
...@@ -2,7 +2,8 @@ ...@@ -2,7 +2,8 @@
"name": "uni-ai-chat", "name": "uni-ai-chat",
"dependencies": { "dependencies": {
"uni-config-center": "file:../../../uni_modules/uni-config-center/uniCloud/cloudfunctions/common/uni-config-center", "uni-config-center": "file:../../../uni_modules/uni-config-center/uniCloud/cloudfunctions/common/uni-config-center",
"uni-sec-check": "file:../../../uni_modules/uni-sec-check/uniCloud/cloudfunctions/common/uni-sec-check" "uni-sec-check": "file:../../../uni_modules/uni-sec-check/uniCloud/cloudfunctions/common/uni-sec-check",
"uni-id-common": "file:../../../uni_modules/uni-id-common/uniCloud/cloudfunctions/common/uni-id-common"
}, },
"extensions": { "extensions": {
"uni-cloud-jql": {}, "uni-cloud-jql": {},
......
// 本文件用于,使用JQL语法操作项目关联的uniCloud空间的数据库,方便开发调试和远程数据库管理
// 编写clientDB的js API(也支持常规js语法,比如var),可以对云数据库进行增删改查操作。不支持uniCloud-db组件写法
// 可以全部运行,也可以选中部分代码运行。点击工具栏上的运行按钮或者按下【F5】键运行代码
// 如果文档中存在多条JQL语句,只有最后一条语句生效
// 如果混写了普通js,最后一条语句需是数据库操作语句
// 此处代码运行不受DB Schema的权限控制,移植代码到实际业务中注意在schema中配好permission
// 不支持clientDB的action
// 数据库查询有最大返回条数限制,详见:https://uniapp.dcloud.net.cn/uniCloud/cf-database.html#limit
// 详细JQL语法,请参考:https://uniapp.dcloud.net.cn/uniCloud/jql.html
// 下面示例查询uni-id-users表的所有数据
db.collection("uni-id-users")
.field('score')
.get()
\ No newline at end of file
{
"bsonType": "object",
"required": [
"user_id",
"score",
"balance"
],
"properties": {
"_id": {
"description": "ID,系统自动生成"
},
"user_id": {
"bsonType": "string",
"description": "用户id,参考uni-id-users表"
},
"score": {
"bsonType": "int",
"description": "本次变化的积分"
},
"type": {
"bsonType": "int",
"enum": [
1,
2
],
"description": "积分类型 1:收入 2:支出"
},
"balance": {
"bsonType": "int",
"description": "变化后的积分余额"
},
"comment": {
"bsonType": "string",
"description": "备注,说明积分新增、消费的缘由",
"trim": "both"
},
"create_date": {
"bsonType": "timestamp",
"description": "创建时间",
"forceDefaultValue": {
"$env": "now"
}
}
},
"version": "0.0.1"
}
\ No newline at end of file
## 1.2.2(2023-01-28)
- 修复 运行/打包 控制台警告问题
## 1.2.1(2022-09-05)
- 修复 当 text 超过 max-num 时,badge 的宽度计算是根据 text 的长度计算,更改为 css 计算实际展示宽度,详见:[https://ask.dcloud.net.cn/question/150473](https://ask.dcloud.net.cn/question/150473)
## 1.2.0(2021-11-19)
- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-badge](https://uniapp.dcloud.io/component/uniui/uni-badge)
## 1.1.7(2021-11-08)
- 优化 升级ui
- 修改 size 属性默认值调整为 small
- 修改 type 属性,默认值调整为 error,info 替换 default
## 1.1.6(2021-09-22)
- 修复 在字节小程序上样式不生效的 bug
## 1.1.5(2021-07-30)
- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
## 1.1.4(2021-07-29)
- 修复 去掉 nvue 不支持css 的 align-self 属性,nvue 下不暂支持 absolute 属性
## 1.1.3(2021-06-24)
- 优化 示例项目
## 1.1.1(2021-05-12)
- 新增 组件示例地址
## 1.1.0(2021-05-12)
- 新增 uni-badge 的 absolute 属性,支持定位
- 新增 uni-badge 的 offset 属性,支持定位偏移
- 新增 uni-badge 的 is-dot 属性,支持仅显示有一个小点
- 新增 uni-badge 的 max-num 属性,支持自定义封顶的数字值,超过 99 显示99+
- 优化 uni-badge 属性 custom-style, 支持以对象形式自定义样式
## 1.0.7(2021-05-07)
- 修复 uni-badge 在 App 端,数字小于10时不是圆形的bug
- 修复 uni-badge 在父元素不是 flex 布局时,宽度缩小的bug
- 新增 uni-badge 属性 custom-style, 支持自定义样式
## 1.0.6(2021-02-04)
- 调整为uni_modules目录规范
<template>
<view class="uni-badge--x">
<slot />
<text v-if="text" :class="classNames" :style="[positionStyle, customStyle, dotStyle]"
class="uni-badge" @click="onClick()">{{displayValue}}</text>
</view>
</template>
<script>
/**
* Badge 数字角标
* @description 数字角标一般和其它控件(列表、9宫格等)配合使用,用于进行数量提示,默认为实心灰色背景
* @tutorial https://ext.dcloud.net.cn/plugin?id=21
* @property {String} text 角标内容
* @property {String} size = [normal|small] 角标内容
* @property {String} type = [info|primary|success|warning|error] 颜色类型
* @value info 灰色
* @value primary 蓝色
* @value success 绿色
* @value warning 黄色
* @value error 红色
* @property {String} inverted = [true|false] 是否无需背景颜色
* @property {Number} maxNum 展示封顶的数字值,超过 99 显示 99+
* @property {String} absolute = [rightTop|rightBottom|leftBottom|leftTop] 开启绝对定位, 角标将定位到其包裹的标签的四角上
* @value rightTop 右上
* @value rightBottom 右下
* @value leftTop 左上
* @value leftBottom 左下
* @property {Array[number]} offset 距定位角中心点的偏移量,只有存在 absolute 属性时有效,例如:[-10, -10] 表示向外偏移 10px,[10, 10] 表示向 absolute 指定的内偏移 10px
* @property {String} isDot = [true|false] 是否显示为一个小点
* @event {Function} click 点击 Badge 触发事件
* @example <uni-badge text="1"></uni-badge>
*/
export default {
name: 'UniBadge',
emits: ['click'],
props: {
type: {
type: String,
default: 'error'
},
inverted: {
type: Boolean,
default: false
},
isDot: {
type: Boolean,
default: false
},
maxNum: {
type: Number,
default: 99
},
absolute: {
type: String,
default: ''
},
offset: {
type: Array,
default () {
return [0, 0]
}
},
text: {
type: [String, Number],
default: ''
},
size: {
type: String,
default: 'small'
},
customStyle: {
type: Object,
default () {
return {}
}
}
},
data() {
return {};
},
computed: {
width() {
return String(this.text).length * 8 + 12
},
classNames() {
const {
inverted,
type,
size,
absolute
} = this
return [
inverted ? 'uni-badge--' + type + '-inverted' : '',
'uni-badge--' + type,
'uni-badge--' + size,
absolute ? 'uni-badge--absolute' : ''
].join(' ')
},
positionStyle() {
if (!this.absolute) return {}
let w = this.width / 2,
h = 10
if (this.isDot) {
w = 5
h = 5
}
const x = `${- w + this.offset[0]}px`
const y = `${- h + this.offset[1]}px`
const whiteList = {
rightTop: {
right: x,
top: y
},
rightBottom: {
right: x,
bottom: y
},
leftBottom: {
left: x,
bottom: y
},
leftTop: {
left: x,
top: y
}
}
const match = whiteList[this.absolute]
return match ? match : whiteList['rightTop']
},
dotStyle() {
if (!this.isDot) return {}
return {
width: '10px',
minWidth: '0',
height: '10px',
padding: '0',
borderRadius: '10px'
}
},
displayValue() {
const {
isDot,
text,
maxNum
} = this
return isDot ? '' : (Number(text) > maxNum ? `${maxNum}+` : text)
}
},
methods: {
onClick() {
this.$emit('click');
}
}
};
</script>
<style lang="scss" >
$uni-primary: #2979ff !default;
$uni-success: #4cd964 !default;
$uni-warning: #f0ad4e !default;
$uni-error: #dd524d !default;
$uni-info: #909399 !default;
$bage-size: 12px;
$bage-small: scale(0.8);
.uni-badge--x {
/* #ifdef APP-NVUE */
// align-self: flex-start;
/* #endif */
/* #ifndef APP-NVUE */
display: inline-block;
/* #endif */
position: relative;
}
.uni-badge--absolute {
position: absolute;
}
.uni-badge--small {
transform: $bage-small;
transform-origin: center center;
}
.uni-badge {
/* #ifndef APP-NVUE */
display: flex;
overflow: hidden;
box-sizing: border-box;
font-feature-settings: "tnum";
min-width: 20px;
/* #endif */
justify-content: center;
flex-direction: row;
height: 20px;
padding: 0 4px;
line-height: 18px;
color: #fff;
border-radius: 100px;
background-color: $uni-info;
background-color: transparent;
border: 1px solid #fff;
text-align: center;
font-family: 'Helvetica Neue', Helvetica, sans-serif;
font-size: $bage-size;
/* #ifdef H5 */
z-index: 999;
cursor: pointer;
/* #endif */
&--info {
color: #fff;
background-color: $uni-info;
}
&--primary {
background-color: $uni-primary;
}
&--success {
background-color: $uni-success;
}
&--warning {
background-color: $uni-warning;
}
&--error {
background-color: $uni-error;
}
&--inverted {
padding: 0 5px 0 0;
color: $uni-info;
}
&--info-inverted {
color: $uni-info;
background-color: transparent;
}
&--primary-inverted {
color: $uni-primary;
background-color: transparent;
}
&--success-inverted {
color: $uni-success;
background-color: transparent;
}
&--warning-inverted {
color: $uni-warning;
background-color: transparent;
}
&--error-inverted {
color: $uni-error;
background-color: transparent;
}
}
</style>
{
"id": "uni-badge",
"displayName": "uni-badge 数字角标",
"version": "1.2.2",
"description": "数字角标(徽章)组件,在元素周围展示消息提醒,一般用于列表、九宫格、按钮等地方。",
"keywords": [
"",
"badge",
"uni-ui",
"uniui",
"数字角标",
"徽章"
],
"repository": "https://github.com/dcloudio/uni-ui",
"engines": {
"HBuilderX": ""
},
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
"type": "component-vue"
},
"uni_modules": {
"dependencies": ["uni-scss"],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "y",
"联盟": "y"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
}
}
\ No newline at end of file
## Badge 数字角标
> **组件名:uni-badge**
> 代码块: `uBadge`
数字角标一般和其它控件(列表、9宫格等)配合使用,用于进行数量提示,默认为实心灰色背景,
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-badge)
#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839
## 0.6.4(2023-01-16)
- 修复 部分情况下APP端无法获取验证码的问题
## 0.6.3(2023-01-11)
- 修复 抖音小程序无法显示的Bug
- 修复 刷新时兼容 device_uuid
## 0.6.1(2022-06-23)
- 修复:部分返回值,不符合响应体规范的问题
## 0.6.0(2022-05-27)
- 新增:支持在`uni-config-center`中根据场景值配置
- 修复:弹窗式验证码,输入内容后点击取消,重新打开验证码的值仍然存在的问题
## 0.5.2(2022-05-19)
- 修复在Vue3的兼容问题
## 0.5.1(2022-05-18)
- 修复在某些情况下微信小程序端验证码显示错误的问题
## 0.5.0(2022-05-17)
- 新增支持在`uni-captcha-co`->`config`配置验证码
## 0.4.1(2022-05-16)
- 新增示例项目
## 0.4.0(2022-05-16)
- 集成创建、刷新、显示验证码的云端一体验证码组件
- 云对象`uni-captcha-co`集成获取验证码的api,`getImageCaptcha`
## 0.3.1(2022-05-13)
- 新增 返回值符合响应体规范
## 0.3.0(2022-05-13)
- 新增 支持 uni-config-center 配置
## 0.2.2(2022-04-25)
- 修复 0.2.1 版本引起的使用 image 组件验证码不显示的Bug
## 0.2.1(2022-04-18)
- 更新 优化字体
## 0.2.0(2022-04-14)
- 新增 使用 svg 表现形式更好
- 新增 使用字体,可以任意替换默认字体
- 新增 支持设置字体大小
- 新增 支持忽略某些字符
- 注意 更新之后请重新上传公共模块
## 0.1.0(2021-03-01)
- 调整为uni_modules目录规范
<template>
<view class="captcha-box">
<view class="captcha-img-box">
<uni-icons class="loding" size="20px" color="#BBB" v-if="loging" type="spinner-cycle"></uni-icons>
<image class="captcha-img" :class="{opacity:loging}" @click="getImageCaptcha" :src="captchaBase64"
mode="widthFix"></image>
</view>
<input @blur="focusCaptchaInput = false" :focus="focusCaptchaInput" type="text" class="captcha"
:inputBorder="false" maxlength="4" v-model="val" placeholder="请输入验证码">
</view>
</template>
<script>
export default {
props: {
modelValue:String,
value:String,
scene: {
type: String,
default () {
return ""
}
},
focus: {
type: Boolean,
default () {
return false
}
}
},
computed:{
val:{
get(){
return this.value||this.modelValue
},
set(value){
// console.log(value);
// TODO 兼容 vue2
// #ifdef VUE2
this.$emit('input', value);
// #endif
// TODO 兼容 vue3
// #ifdef VUE3
this.$emit('update:modelValue', value)
// #endif
}
}
},
data() {
return {
focusCaptchaInput: false,
captchaBase64: "",
loging: false
};
},
watch: {
scene: {
handler(scene) {
if (scene) {
this.getImageCaptcha(this.focus)
} else {
uni.showToast({
title: 'scene不能为空',
icon: 'none'
});
}
},
immediate:true
}
},
methods: {
getImageCaptcha(focus = true) {
this.loging = true
if (focus) {
this.val = ''
this.focusCaptchaInput = true
}
const uniIdCo = uniCloud.importObject("uni-captcha-co", {
customUI: true
})
uniIdCo.getImageCaptcha({
scene: this.scene
}).then(result => {
// console.log(result);
this.captchaBase64 = result.captchaBase64
})
.catch(e => {
uni.showToast({
title: e.message,
icon: 'none'
});
}).finally(e => {
this.loging = false
})
}
}
}
</script>
<style lang="scss" scoped>
.captcha-box {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
align-items: center;
justify-content: flex-end;
flex: 1;
}
.captcha-img-box,
.captcha {
height: 44px;
line-height: 44px;
}
.captcha-img-box {
position: relative;
background-color: #FEFAE7;
}
.captcha {
background-color: #F8F8F8;
font-size: 14px;
flex: 1;
padding: 0 20rpx;
margin-left: 20rpx;
/* #ifndef APP-NVUE */
box-sizing: border-box;
/* #endif */
}
.captcha-img-box,
.captcha-img,
.loding {
height: 44px !important;
width: 100px;
}
.captcha-img{
cursor: pointer;
}
.loding {
z-index: 9;
color: #bbb;
position: absolute;
text-align: center;
line-height: 45px;
animation: rotate 1s linear infinite;
}
.opacity {
opacity: 0.5;
}
@keyframes rotate {
from {
transform: rotate(0deg)
}
to {
transform: rotate(360deg)
}
}
</style>
\ No newline at end of file
<template>
<uni-popup ref="popup" type="center">
<view class="popup-captcha">
<view class="content">
<text class="title">{{title}}</text>
<uni-captcha :focus="focus" :scene="scene" v-model="val"></uni-captcha>
</view>
<view class="button-box">
<view @click="close" class="btn">取消</view>
<view @click="confirm" class="btn confirm">确认</view>
</view>
</view>
</uni-popup>
</template>
<script>
export default {
data() {
return {
focus: false
}
},
props: {
modelValue:String,
value:String,
scene: {
type: String,
default () {
return ""
}
},
title: {
type: String,
default () {
return ""
}
},
},
computed:{
val:{
get(){
return this.value||this.modelValue
},
set(value){
// console.log(value);
// TODO 兼容 vue2
// #ifdef VUE2
this.$emit('input', value);
// #endif
// TODO 兼容 vue3
// #ifdef VUE3
this.$emit('update:modelValue', value)
// #endif
}
}
},
methods: {
open() {
this.focus = true
this.val = ""
this.$refs.popup.open()
},
close() {
this.focus = false
this.$refs.popup.close()
},
confirm() {
if(!this.val){
return uni.showToast({
title: '请填写验证码',
icon: 'none'
});
}
this.close()
this.$emit('confirm')
}
}
}
</script>
<style lang="scss" scoped>
/* #ifndef APP-NVUE */
view {
display: flex;
flex-direction: column;
}
/* #endif */
.popup-captcha {
/* #ifndef APP-NVUE */
display: flex;
max-width: 600px;
/* #endif */
width: 600rpx;
padding-bottom: 0;
background-color: #FFF;
border-radius: 10px;
flex-direction: column;
position: relative;
}
.popup-captcha .content {
padding: 1.3em 0.8em;
}
.popup-captcha .title {
text-align: center;
word-wrap: break-word;
word-break: break-all;
white-space: pre-wrap;
font-weight: 400;
font-size: 18px;
overflow: hidden;
text-overflow: ellipsis;
color: #111;
margin-bottom: 15px;
}
.button-box {
height: 44px;
border-top: solid 1px #eee;
flex-direction: row;
align-items: center;
justify-content: space-around;
}
.button-box ,.btn{
height: 44px;
line-height: 44px;
}
.button-box .btn{
flex: 1;
margin: 1px;
text-align: center;
}
.button-box .confirm{
color: #007aff;
border-left: solid 1px #eee;
}
</style>
{
"id": "uni-captcha",
"displayName": "uni-captcha",
"version": "0.6.4",
"description": "云端一体图形验证码组件",
"keywords": [
"captcha",
"图形验证码",
"人机验证",
"防刷",
"防脚本"
],
"repository": "https://gitee.com/dcloud/uni-captcha",
"engines": {
"HBuilderX": "^3.1.0"
},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "",
"type": "unicloud-template-function"
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "u",
"app-nvue": "u"
},
"H5-mobile": {
"Safari": "u",
"Android Browser": "u",
"微信浏览器(Android)": "u",
"QQ浏览器(Android)": "u"
},
"H5-pc": {
"Chrome": "u",
"IE": "u",
"Edge": "u",
"Firefox": "u",
"Safari": "u"
},
"小程序": {
"微信": "u",
"阿里": "u",
"百度": "u",
"字节跳动": "u",
"QQ": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
},
"Vue": {
"vue2": "y",
"vue3": "u"
}
}
}
}
}
\ No newline at end of file
<h2>
文档已移至 <a href="https://uniapp.dcloud.io/uniCloud/uni-captcha.html" target="_blank">uni-captcha文档</a>
</h2>
\ No newline at end of file
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
../../../../../../uni-config-center/uniCloud/cloudfunctions/common/uni-config-center
\ No newline at end of file
{
"name": "uni-captcha",
"version": "0.6.4",
"description": "uni-captcha",
"main": "index.js",
"homepage": "https://ext.dcloud.net.cn/plugin?id=4048",
"repository": {
"type": "git",
"url": "git+https://gitee.com/dcloud/uni-captcha"
},
"author": "DCloud",
"license": "Apache-2.0",
"dependencies": {
"uni-config-center": "file:../../../../../uni-config-center/uniCloud/cloudfunctions/common/uni-config-center"
}
}
\ No newline at end of file
module.exports = {
"image-captcha":{
"width": 150, //图片宽度
"height": 44, //图片高度
"background": "#FFFAE8", //验证码背景色,设置空字符`''`不使用背景颜色
// "size": 4, //验证码长度,最多 6 个字符
// "noise": 4, //验证码干扰线条数
// "color": false, //字体是否使用随机颜色,当设置`background`后恒为`true`
// "fontSize": 40, //字体大小
// "ignoreChars": '', //忽略那些字符
// "mathExpr": false, //是否使用数学表达式
// "mathMin": 1, //表达式所使用的最小数字
// "mathMax": 9, //表达式所使用的最大数字
// "mathOperator": '' //表达式所使用的运算符,支持 `+`、`-`。不传随机使用
// "expiresDate":180 //验证码过期时间(s)
}
}
\ No newline at end of file
// 开发文档: https://uniapp.dcloud.net.cn/uniCloud/cloud-obj
//导入验证码公共模块
const uniCaptcha = require('uni-captcha')
//获取数据库对象
const db = uniCloud.database();
//获取数据表opendb-verify-codes对象
const verifyCodes = db.collection('opendb-verify-codes')
module.exports = {
async getImageCaptcha({
scene
}) {
//获取设备id
let {
deviceId,
platform
} = this.getClientInfo();
//根据:设备id、场景值、状态,查找记录是否存在
let res = await verifyCodes.where({
scene,
deviceId,
state: 0
}).limit(1).get()
//如果已存在则调用刷新接口,反之调用插件接口
let action = res.data.length ? 'refresh' : 'create'
//执行并返回结果
//导入配置,配置优先级说明:此处配置 > uni-config-center
return await uniCaptcha[action]({
scene, //来源客户端传递,表示:使用场景值,用于防止不同功能的验证码混用
uniPlatform: platform
})
}
}
{
"name": "uni-captcha-co",
"dependencies": {
"uni-captcha": "file:../common/uni-captcha",
"uni-config-center": "file:../../../../uni-config-center/uniCloud/cloudfunctions/common/uni-config-center"
},
"extensions": {
"uni-cloud-jql": {}
}
}
\ No newline at end of file
{
"bsonType": "object",
"properties": {
"_id": {
"description": "ID,系统自动生成"
},
"code": {
"bsonType": "string",
"description": "验证码"
},
"create_date": {
"bsonType": "timestamp",
"description": "创建时间"
},
"device_uuid": {
"bsonType": "string",
"description": "设备UUID,常用于图片验证码"
},
"email": {
"bsonType": "string",
"description": "邮箱"
},
"expired_date": {
"bsonType": "timestamp",
"description": "过期时间"
},
"ip": {
"bsonType": "string",
"description": "请求时客户端IP地址"
},
"mobile": {
"bsonType": "string",
"description": "手机号码"
},
"scene": {
"bsonType": "string",
"description": "使用验证码的场景,如:login, bind, unbind, pay"
},
"state": {
"bsonType": "int",
"description": "验证状态:0 未验证、1 已验证、2 已作废"
}
},
"required": []
}
\ No newline at end of file
## 1.0.1(2023-03-02)
- 修复 方法名错误
{
"id": "uni-cloud-s2s",
"displayName": "服务空间与服务器安全通讯模块",
"version": "1.0.1",
"description": "用于解决服务空间与服务器通讯时互相信任问题",
"keywords": [
"安全通讯",
"服务器请求云函数",
"云函数请求服务器"
],
"repository": "",
"engines": {
"HBuilderX": "^3.1.0"
},
"dcloudext": {
"type": "unicloud-template-function",
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": ""
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"Vue": {
"vue2": "u",
"vue3": "u"
},
"App": {
"app-vue": "u",
"app-nvue": "u"
},
"H5-mobile": {
"Safari": "u",
"Android Browser": "u",
"微信浏览器(Android)": "u",
"QQ浏览器(Android)": "u"
},
"H5-pc": {
"Chrome": "u",
"IE": "u",
"Edge": "u",
"Firefox": "u",
"Safari": "u"
},
"小程序": {
"微信": "u",
"阿里": "u",
"百度": "u",
"字节跳动": "u",
"QQ": "u",
"钉钉": "u",
"快手": "u",
"飞书": "u",
"京东": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
}
}
}
}
}
\ No newline at end of file
# uni-cloud-s2s
文档见:[外部服务器如何与uniCloud安全通讯](https://uniapp.dcloud.net.cn/uniCloud/uni-cloud-s2s.html)
\ No newline at end of file
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("crypto"),t=require("path");function s(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}require("fs");var o=s(e),n=s(t);const i="uni-cloud-s2s",r={code:5e4,message:"Config error"},c={code:51e3,message:"Access denied"};class a extends Error{constructor(e){super(e.message),this.errMsg=e.message||"",this.code=this.errCode=e.code,this.errSubject=e.subject,this.forceReturn=e.forceReturn||!1,this.cause=e.cause,Object.defineProperties(this,{message:{get(){return this.errMsg},set(e){this.errMsg=e}}})}toJSON(e=0){if(!(e>=10))return e++,{errCode:this.errCode,errMsg:this.errMsg,errSubject:this.errSubject,cause:this.cause&&this.cause.toJSON?this.cause.toJSON(e):this.cause}}}const d=Object.prototype.toString;const h=50002,u=Object.create(null);["string","boolean","number","null"].forEach((e=>{u[e]=function(t,s){if(function(e){return d.call(e).slice(8,-1).toLowerCase()}(t)!==e)return{code:h,message:`${s} is invalid`}}}));const f="Unicloud-S2s-Authorization";class g{constructor(e){const{config:t}=e||{};this.config=t;const{connectCode:s}=t||{};if(this.connectCode=s,!s||"string"!=typeof s)throw new a({subject:i,code:r.code,message:"Invalid connectCode in config"})}getHeadersValue(e={},t,s){const o=Object.keys(e||{}).find((e=>e.toLowerCase()===t.toLowerCase()));return o?e[o]:s}verifyHttpInfo(e){const t=this.getHeadersValue(e.headers,f,""),[s="",o=""]=t.split(" ");if(s.toLowerCase()==="CONNECTCODE".toLowerCase()&&o===this.config.connectCode)return!0;throw new a({subject:i,code:c.code,message:`Invalid CONNECTCODE in headers['${f}']`})}getSecureHeaders(e){return{[f]:`CONNECTCODE ${this.config.connectCode}`}}}function l(e){return function(t){const{content:s,signKey:n}=t||{};return o.default.createHash(e).update(s+"\n"+n).digest("hex")}}const p={md5:l("md5"),sha1:l("sha1"),sha256:l("md5"),"hmac-sha256":function(e){const{content:t,signKey:s}=e||{};return o.default.createHmac("sha256",s).update(t).digest("hex")}};function m(e){const{timestamp:t,data:s={},signKey:o,hashMethod:n="hmac-sha256"}=e||{},i=p[n],r=["number","string","boolean"],c=Object.keys(s).sort(),a=[];for(let e=0;e<c.length;e++){const t=c[e],o=s[t],n=typeof o;r.includes(n)&&a.push(`${t}=${o}`)}return i({content:`${t}\n${a.join("&")}`,signKey:o})}class w{constructor(e){const{config:t}=e||{};this.config=t;const{signKey:s,hashMethod:o="hmac-sha256",timeDiffTolerance:n=60}=t;if(!p[o])throw new a({subject:i,code:r.code,message:`Invalid hashMethod in config, expected "md5", "sha1", "sha256" or "hmac-sha256", got "${o}"`});if(!s||"string"!=typeof s)throw new a({subject:i,code:r.code,message:"Invalid signKey in config"});this.signKey=s,this.hashMethod=o,this.timeDiffTolerance=n}getHttpHeaders(e){return e.headers||{}}getHeadersValue(e,t,s){const o=Object.keys(e||{}).find((e=>e.toLowerCase()===t.toLowerCase()));return o?e[o]:s}getHttpData(e){const t=e.httpMethod.toLowerCase(),s=this.getHttpHeaders(e),o=this.getHeadersValue(s,"Content-Type","");if("get"===t)return e.queryStringParameters;if("post"!==t)throw new a({subject:i,code:c.code,message:`Invalid http method, expected "POST" or "get", got "${t}"`});if(0===o.indexOf("application/json"))return JSON.parse(e.body);if(0===o.indexOf("application/x-www-form-urlencoded"))return require("querystring").parse(e.body);throw new a({subject:i,code:c.code,message:`Invalid content type of POST method, expected "application/json" or "application/x-www-form-urlencoded", got "${o}"`})}verifyHttpInfo(e){const t=e.headers||{},s=this.getHeadersValue(t,"Unicloud-S2s-Timestamp","0");let[o,n]=this.getHeadersValue(t,"Unicloud-S2s-Signature","").split(" ");if(o=o.toLowerCase(),o!==this.hashMethod)throw new a({subject:i,code:c.code,message:`Invalid hash method, expected "${this.hashMethod}", got "${o}"`});const r=parseInt(s),d=Date.now();if(Math.abs(d-r)>1e3*this.timeDiffTolerance)throw new a({subject:i,code:c.code,message:`Invalid timestamp, server timestamp is ${d}, ${r} exceed max timeDiffTolerance(${this.timeDiffTolerance} seconds)`});return m({timestamp:r,data:this.getHttpData(e),signKey:this.signKey,hashMethod:this.hashMethod})===n}getSecureHeaders(e){const{data:t}=e||{},s=Date.now(),o=m({timestamp:s,data:t,signKey:this.signKey,hashMethod:this.hashMethod});return{"Unicloud-S2s-Timestamp":s+"","Unicloud-S2s-Signature":this.hashMethod+" "+o}}}const y=require("uni-config-center")({pluginId:i});class b{constructor(){this.config=y.config();const e=n.default.resolve(require.resolve("uni-config-center"),i,"config.json");if(!this.config)throw new a({subject:i,code:r.code,message:`${i} config required, please check your config file: ${e}`});if("connectCode"===this.config.type)this.verifier=new g({config:this.config});else{if(!function(e){return"sign"===e.type}(this.config))throw new a({subject:i,code:r.code,message:`Invalid ${i} config, expected policy is "code" or "sign", got ${this.config.policy}`});this.verifier=new w({config:this.config})}}verifyHttpInfo(e){if(!e)throw new a({subject:i,code:c.code,message:"Access denied, httpInfo required"});return this.verifier.verifyHttpInfo(e)}getSecureHeaders(e){return this.verifier.getSecureHeaders(e)}}exports.getSecureHeaders=function(e){return(new b).getSecureHeaders(e)},exports.verifyHttpInfo=function(e){const t=(new b).verifyHttpInfo(e);if(!t)throw new a({subject:i,code:c.code,message:c.message});return t};
../../../../../../uni-config-center/uniCloud/cloudfunctions/common/uni-config-center
\ No newline at end of file
{
"name": "uni-cloud-s2s",
"version": "1.0.1",
"description": "",
"keywords": [],
"author": "DCloud",
"main": "index.js",
"dependencies": {
"uni-config-center": "file:../../../../../uni-config-center/uniCloud/cloudfunctions/common/uni-config-center"
}
}
\ No newline at end of file
{ {
"contentSecurity":false "contentSecurity":false,
"spentScore":0,
"earnedScore":{
"ad":3,
"price":3
},
"llm":{},
"chatCompletionOptions":{
"tokensToGenerate":512
}
} }
\ No newline at end of file
{ {
"passwordSecret": [{ "passwordSecret": "passwordSecret",
"type": "hmac-sha256", "tokenSecret": "tokenSecret",
"version": 1
}],
"passwordStrength": "medium",
"tokenSecret": "",
"requestAuthSecret": "",
"tokenExpiresIn": 7200, "tokenExpiresIn": 7200,
"tokenExpiresThreshold": 3600, "tokenExpiresThreshold": 600,
"passwordErrorLimit": 6, "passwordErrorLimit": 6,
"bindTokenToDevice": false,
"passwordErrorRetryTime": 3600, "passwordErrorRetryTime": 3600,
"autoSetInviteCode": false, "autoSetInviteCode": false,
"forceInviteCode": false, "forceInviteCode": false,
"idCardCertifyLimit": 1, "preferedAppPlatform": "app",
"realNameCertifyLimit": 5,
"sensitiveInfoEncryptSecret": "",
"frvNeedAlivePhoto": false,
"userRegisterDefaultRole": [],
"app": { "app": {
"tokenExpiresIn": 2592000, "tokenExpiresIn": 2592000,
"tokenExpiresThreshold": 864000,
"oauth": { "oauth": {
"weixin": { "weixin": {
"appid": "", "appid": "填写来源微信开放平台https://open.weixin.qq.com/创建的应用的appid",
"appsecret": "" "appsecret": "填写来源微信开放平台https://open.weixin.qq.com/创建的应用的appsecret"
},
"qq": {
"appid": "",
"appsecret": ""
}, },
"apple": { "apple": {
"bundleId": "" "bundleId": "苹果开发者后台获取的bundleId"
} }
} }
}, },
"web": { "web": {
"tokenExpiresIn": 7200,
"tokenExpiresThreshold": 3600,
"oauth": { "oauth": {
"weixin-h5": { "h5-weixin": {
"appid": "", "appid": "微信浏览器内微信登录,所用的微信公众号appid",
"appsecret": "" "appsecret": "微信公众号后台获取的appsecret"
}, },
"weixin-web": { "web-weixin": {
"appid": "", "appid": "手机微信扫码登录,所用的微信开放平台(https://open.weixin.qq.com/)-网站应用的appid",
"appsecret": "" "appsecret": "微信开放平台-网站应用的appsecret"
} }
} }
}, },
"mp-weixin": { "mp-weixin": {
"tokenExpiresIn": 259200,
"tokenExpiresThreshold": 86400,
"oauth": { "oauth": {
"weixin": { "weixin": {
"appid": "", "appid": "",
...@@ -58,44 +41,25 @@ ...@@ -58,44 +41,25 @@
} }
} }
}, },
"mp-qq": {
"tokenExpiresIn": 259200,
"tokenExpiresThreshold": 86400,
"oauth": {
"qq": {
"appid": "",
"appsecret": ""
}
}
},
"mp-alipay": { "mp-alipay": {
"tokenExpiresIn": 259200,
"tokenExpiresThreshold": 86400,
"oauth": { "oauth": {
"alipay": { "alipay": {
"appid": "", "appid": "支付宝小程序登录用到的appid、privateKey请参考支付宝小程序的文档进行设置或者获取,https://opendocs.alipay.com/open/291/105971#LDsXr",
"privateKey": "", "privateKey": "支付宝小程序登录用到的appid、privateKey请参考支付宝小程序的文档进行设置或者获取,https://opendocs.alipay.com/open/291/105971#LDsXr"
"keyType": "PKCS8"
} }
} }
}, },
"service": { "service": {
"sms": { "sms": {
"name": "", "name": "应用名称,对应短信模版的name",
"codeExpiresIn": 180, "codeExpiresIn": 300,
"smsKey": "", "smsKey": "短信密钥key,开通短信服务处可以看到",
"smsSecret": "", "smsSecret": "短信密钥secret,开通短信服务处可以看到"
"scene": {
"bind-mobile-by-sms": {
"templateId": "",
"codeExpiresIn": 240
}
}
}, },
"univerify": { "univerify": {
"appid": "", "appid": "当前应用的appid,使用云函数URL化,此项必须配置",
"apiKey": "", "apiKey": "apiKey 和 apiSecret 在开发者中心获取,开发者中心:https://dev.dcloud.net.cn/uniLogin/index?type=0,文档:https://ask.dcloud.net.cn/article/37965",
"apiSecret": "" "apiSecret": ""
} }
} }
} }
\ No newline at end of file
// 你的应用的 appid,比如:__UNI_123123
const appId = "";
// 用户注册后默认积分
const defaultScore = 0;
// 钩子函数示例 hooks/index.js
function beforeRegister({
userRecord,
clientInfo
} = {}) {
if (clientInfo.appId === appId) {
userRecord.score = defaultScore
}
return userRecord // 务必返回处理后的userRecord
}
module.exports = {
beforeRegister
}
\ No newline at end of file
{
"schedule": {
"appid": {
"enable": true,
"weixin-mp": {
"enable": true,
"tasks": ["accessToken"]
}
}
},
"ipWhiteList": ["0.0.0.0"]
}
## 1.0.3(2022-09-16)
- 可以使用 uni-scss 控制主题色
## 1.0.2(2022-06-30)
- 优化 在 uni-forms 中的依赖注入方式
## 1.0.1(2022-02-07)
- 修复 multiple 为 true 时,v-model 的值为 null 报错的 bug
## 1.0.0(2021-11-19)
- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-data-checkbox](https://uniapp.dcloud.io/component/uniui/uni-data-checkbox)
## 0.2.5(2021-08-23)
- 修复 在uni-forms中 modelValue 中不存在当前字段,当前字段必填写也不参与校验的问题
## 0.2.4(2021-08-17)
- 修复 单选 list 模式下 ,icon 为 left 时,选中图标不显示的问题
## 0.2.3(2021-08-11)
- 修复 在 uni-forms 中重置表单,错误信息无法清除的问题
## 0.2.2(2021-07-30)
- 优化 在uni-forms组件,与label不对齐的问题
## 0.2.1(2021-07-27)
- 修复 单选默认值为0不能选中的Bug
## 0.2.0(2021-07-13)
- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
## 0.1.11(2021-07-06)
- 优化 删除无用日志
## 0.1.10(2021-07-05)
- 修复 由 0.1.9 引起的非 nvue 端图标不显示的问题
## 0.1.9(2021-07-05)
- 修复 nvue 黑框样式问题
## 0.1.8(2021-06-28)
- 修复 selectedTextColor 属性不生效的Bug
## 0.1.7(2021-06-02)
- 新增 map 属性,可以方便映射text/value属性
## 0.1.6(2021-05-26)
- 修复 不关联服务空间的情况下组件报错的Bug
## 0.1.5(2021-05-12)
- 新增 组件示例地址
## 0.1.4(2021-04-09)
- 修复 nvue 下无法选中的问题
## 0.1.3(2021-03-22)
- 新增 disabled属性
## 0.1.2(2021-02-24)
- 优化 默认颜色显示
## 0.1.1(2021-02-24)
- 新增 支持nvue
## 0.1.0(2021-02-18)
- “暂无数据”显示居中
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
## 1.0.16(2023-04-25)
- 新增maxTokenLength配置,用于限制数据库用户记录token数组的最大长度
## 1.0.15(2023-04-06) ## 1.0.15(2023-04-06)
- 修复部分语言国际化出错的Bug - 修复部分语言国际化出错的Bug
## 1.0.14(2023-03-07) ## 1.0.14(2023-03-07)
......
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册