- 小程序备案指南(企业备案),持续更新
在mp后台: 1:未上架的小程 首页--小程序发布流程--小程序备案(查看能否备案)。 说明:此页面是未发布小程序前的首页页面,发布后的不一样,不要纠结找不找得到、没有这个页面。已经发布的看下方第二张图。 可以备案: [图片] 2:已上架的小程 可以备案: 小程序管理后台顶部会提示“小程序需补充备案信息”的提醒,点击【去备案】即可进入备案流程 或在设置--基本设置--小程序备案(去备案) 不能备案: 设置--基本设置--小程序备案(暂未对存量小程序开放) [图片] 企业小程序备案准备材料: 营业执照(副本扫描件或加盖公章的复印件,建议用副本扫描件,在上面加上**小程序备案所用,他用无效)。法人身份(最好是法人,负责人的也可以)证正反面照片,彩色的,拍照要拍全。管理员个人信息,姓名,身份证号,电话,备用电话,常用邮箱。(建议管理人员和负责人是同一个人)地址填写,最好是营业执照上地址,也可以是常用地址。前置审批/专项审批(具体可查看https://developers.weixin.qq.com/miniprogram/product/record_guidelines.html)。补充材料:根据规则提供包括但不限于授权书、党建证明、居住证、情况说明、承诺书等。互联网信息服务备案承诺书(单位)。资料提前准备好,需要法人扫码验证(和小程序认证差不多)。根据不同地区,准备资料可能有所差异,详细需要什么资料,审核备案时具体再做补充。如果上述没有看懂,转移这里,官方给出的流程https://developers.weixin.qq.com/miniprogram/product/record_guidelines.html[图片] 备案流程 [图片] 。。。。。。(持续更新详细步骤) 九月八号上午填写备案信息,九月十三号成功备案(本来九月八号当天发验证短信的,用户这边没及时验证,耽搁一天) [图片] 常见信息填写问题 1、备案流程中的主办单位、主体负责人具体指的是谁?主办单位 又称互联网信息服务主办者,主要指内容服务提供者,包括单位(如企业、政府机关等)和个人两类。主体负责人 个人:主体负责人应为主办单位本人。非个人:通常由单位法定代表人担任,如有特殊情况(如法代身份涉密、长期不在国内等)可授权单位高管担任。2、个体户没有公章怎么办?若个体工商户无公章,需要主体负责人手写日期+签名+盖手印+身份证号码,同时请在主体备注处备注“个体工商户无公章”。 3、填写小程序主体信息的通讯地址是指的什么地址?可填写主体证件上的地址,也可填写你实际的办公或住所地址。 若你是个人开发者:需精确到门牌号码,若已是最详细的地址或无门牌号的,在主体备注中说明“通信地址已为最详细”。若你是单位开发者需精确到门牌号码,且至少和主体证件所省份保持一致(如证件住所和通讯地址都是广东省),不能使用特殊符号(如:2#楼2-3-301);若已是最详细的地址,无门牌号的,在主体备注中说明“通信地址已为最详细”。备注:若你是北京地区,通讯地址填写时不能使用特殊符号(如:2#楼2-3-301)。4、什么情况下需要上传居住/暂住证明?当个人主体小程序备案申请人的身份证证件地址与申请小程序备案的省份不一致时,需要提供暂住证或居住证等证明材料。 涉及省份包括:吉林、上海、江苏、浙江、安徽、山东、湖北、广东、四川、贵州、云南。 5、小程序备案主体负责人必须填写法定代表人吗?每个省份管局的要求不一致,请按照备案小程序所属省管局要求进行填写,具体请参考: 类型地区主体负责人不是法定代表人需提供小程序主体负责人授权书吉林、山西、甘肃、江苏、安徽、四川 主体负责人必须是法定代表人:天津、内蒙古、陕西、宁夏、新疆、湖北、湖南、河南、上海、浙江、江西、贵州、重庆、云南、西藏、广西、广东、福建、黑龙江、河北、山东、青海 主体负责人可以不是法定代表人吉林、山西、甘肃、江苏、安徽、四川、海南、北京、辽宁 6、提示:主体负责人与法定代表人不一致,且备案所在地不支持法定代表人授权?你填写的【主体负责人】姓名与营业执照证件上的【法定代表人】姓名不一致,请重新填写,并保持一致。你在小程序备案 -【验证备案类型】页面中 - 主办人信息 - 选择地区中选择的省份,不支持法定代表人授权,【主体负责人】需填写【法定代表人】姓名。备案省份需填写小程序备案主体实际所在地,系统会根据你选择的区域自动匹配当地管局规则。7、在填写负责人手机号、应急手机号、邮箱时,提示:不允许被多人使用?在填写负责人手机号、应急手机号、邮箱时,提示“不允许被多人使用”,一般是出现了个人信息混用的情况,即手机号/应急手机号/邮箱填写的是其他人的信息。 在平台备案系统中,人,手机号,应急手机号,邮箱均一一绑定,同一个人允许为多个小程序备案(同一主体下),可以提交一致的手机号、应急手机号及邮箱,但不能出现不同人共用手机号/邮箱的情况。 小程序负责人授权书、小程序主体负责人授权、互联网信息服务承诺书怎么填写?小程序备案材料示例及填写指引:小程序备案材料示例小程序信息填写相关1、什么是服务内容标识?怎么选?服务内容标识是通信管局对各个行业的分类,平台部分行业类目与管局行业类目名称不完全不一致,建议根据备案小程序实际运营内容尽可能选择对应的服务内容标识。 若你是个人主体,请勿选择经营性质、企业/单位性质、涉及有关主管部门审批等的内容,如不可选择“批发和零售业-零售批发”。若你是单位主体,应选择与主体经营范围、资质相符合的内容,如你是医药公司,可选择“医疗服务-医药”,并上传《互联网药品信息服务许可证》。非政府单位不得选择“政务民生”内容。2、小程序负责人具体是指谁?是小程序管理员吗?个人主体:小程序负责人应为主办人本人。 非个人主体:小程序负责人应为本单位/公司具体负责小程序管理、小程序维护的相关人员。 3、怎么判断备案小程序是否要选择前置审批项?可参考:前置审批类别及审批部门 4、小程序管理员信息填写时,负责人姓名已填写为小程序管理员的姓名,为什么还是提示:负责人与小程序管理员不一致?出现这种提示一般都是第三方服务商协助创建的小程序未完善管理员实名信息,需补充管理员实名信息后才能进行备案,补充指引参考: 小程序MP后台-成员管理-管理员-修改。验证原管理员-填写原管理员身份证信息-扫码验证。绑定新管理员-填写【原管理员的信息】并提交,即完成管理员实名信息补充。相关文档可参考:如何完善小程序实名信息 小程序备案常见问题:https://developers.weixin.qq.com/community/develop/article/doc/000ac251a9c340df3e6073ee566c13 最后祝大家,一次备案成功
2023-10-08 - 升级开发工具之后无法正常运行?
我使用腾讯云的数据万象的包,现在直接运行不了。。。回退开发工具版本就没问题。 [图片]. [图片]
2023-07-02 - 我就很纳闷了,微信小程序为什么要去支持QQ直接打开!!!???
是谁决策的!QQ那边的BUG大家都很清楚,各种BUG各种不理各种恶心。。。。 但是你QQ现在可以直接扫微信小程序码打开微信小程序! 请问!官方的人,你们支持这项工作的时候是拍脑门决定的吗!?!?!!?? 你们都不管一下 API对齐 的问题吗??? 我微信小程序跑得明明就很正常的!现在QQ直接打开微信小程序,很多API不支持!!! 我不需要你QQ扫码直接打开我的微信小程序,OK??? 比如:chooseMedia API,QQ那边的用户扫微信小程序码打开之后根本就用不了,我这边客服一大堆反馈!恶心死了!!! 请QQ那边不要来搞我们微信小程序了。。。你们搞你们的元宇宙吧可以吗?
2022-04-23 - 如何使用微信小程序·云开发的Node.js云函数生成Word文档(2021-10-15更新)
编者按 近期一个云开发项目有生成Word文档的需求,经过搜索,发现并没有小程序·云开发有关生成word文档的案例,因为本人还是本科生且非科班出身,一路摸着石头过河,遇到了不少困难,期间还试图向社区的大佬们求助;花了两天时间才搞定这一百行代码,现在分享给大家。 代码有些糙,希望大佬们不要嫌弃。 一、安装云函数依赖officegen、fs 工欲善其事必先利其器,我们知道云函数代码运行在云端Node.js环境中,因此,理论上来说,Node.js能做的事情,小程序·云开发的云函数基本上也能做到。officegen是Github上一款生成微软Office文档的工具,包括.docx、.xlsx、.pptx三种文件,由于我只用了.docx,本文将以Word文件为例。 https://github.com/Ziv-Barber/officegen [图片] 1. 首先我们在微信开发者工具中 新建一个云函数 => 右键云函数名 => 在终端中打开 [图片] 2. npm安装依赖officegen和fs,为了方便本地调试云函数,我们这里也安装wx-server-sdk。 [图片] 代码如下,请逐个安装,如果安装有问题,可以自行搜索“npm”或“npm taobao 镜像” ;这里不再赘述。 npm i officegen npm i fs npm i wx-server-sdk 3. 在云函数index.js开头写下以下代码,引用我们刚刚安装的包。 const cloud = require('wx-server-sdk') const officegen = require('officegen'); const fs = require('fs'); const docx = officegen('docx'); 二、创建Word文档的内容 文档地址: https://github.com/Ziv-Barber/officegen/blob/master/manual/docx/README.md 1. 首先我们根据文档定义(Ctrl CV)两个函数 //文档生成完成后调用,后来其实发现没啥用 // Officegen calling this function after finishing to generate the docx document: docx.on('finalize', async function (written) { console.log('Finish to create a Microsoft Word document.') }) //生成文档出现问题时调用 // Officegen calling this function to report errors: docx.on('error', function (err) { console.log(err) }) 2. 创建段落API: docx.createP(options) //声明一个创建段落的变量p0bj let pObj = docx.createP(options) //创建一个段落并插入文本 pObj = docx.createP({ align: 'center' //文字对齐方式,center、justify、right;默认为left indentLeft = 1440; // 段落缩进 Indent left 1 inch indentFirstLine = 440; // 首行缩进 }) pObj.addText('你要插入的文字,这里可以时变量', { bold: true, //是否加粗,默认false font_face: 'KaiTi', //字体,这里以“楷体为例”,如果填写了打开文档的电脑没有安装的字体名称,将使用默认字体。能不能用中文,我没试过。 font_size: 19, //字号 color: '595959' //文字颜色 }); 上述例子外,还可以添加下划线、设置斜体、超链接、分页等;还可以编辑页眉和页脚、插入图片等。详见后续代码示例或officegen文档。 3. 插入图片 这里以插入小程序码为例,直接上代码。 要注意的是officegen似乎不支持以buffer形式插入图片,因此要先将图片保存。 //首先定义一个用于保存小程序码图片的函数 //save QR saveFile = function (filePath, fileData) { return new Promise((resolve, reject) => { const wstream = fs.createWriteStream(filePath); wstream.on('open', () => { const blockSize = 128; const nbBlocks = Math.ceil(fileData.length / (blockSize)); for (let i = 0; i < nbBlocks; i += 1) { const currentBlock = fileData.slice( blockSize * i, Math.min(blockSize * (i + 1), fileData.length), ); wstream.write(currentBlock); } wstream.end(); }); wstream.on('error', (err) => { reject(err); }); wstream.on('finish', () => { resolve(true); }); }); } //要获取小程序码,首先要修改云函数config.json文件中的云调用权限 { "permissions": { "openapi": [ "wxacode.getUnlimited" ] } } //在云函数main中获取小程序码 //https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/qr-code/wxacode.get.html const result = await cloud.openapi.wxacode.getUnlimited({ page: 'pages/check/check', //小程序页面地址,必须是线上版本中存在的页面的完整地址 scene: '', //小程序码参数 width: 240, //小程序码的宽度(是个正方形) }) const QRcode = result.buffer await saveFile('/tmp/qr.jpg', QRcode); // 这里的fileData是Buffer类型,关于路径会在第三部分生成Word文件中解释。 //将图片插入到文档中 pObj = docx.createP() //创建段落 pObj.options.indentFirstLine = 440; //首行缩进 pObj.addImage('/tmp/qr.jpg', { //图片文件路径 cx: 140, //长度 cy: 140 //宽度 }); 三、生成Word文件 文档内容完成后,就可以生成文档了。officegen似乎只能生成文件,没有文件buffer的接口,而要上传到小程序·云开发的云存储中,只能使用Buffer或fs.ReadStream,怎么办呢?先把文件保存下来再读取呗。 首先提一下云函数运行环境 https://developers.weixin.qq.com/miniprogram/dev/wxcloud/guide/functions/mechanism.html 云函数运行在云端 Linux 环境中,一个云函数在处理并发请求的时候会创建多个云函数实例,每个云函数实例之间相互隔离,没有公用的内存或硬盘空间。云函数实例的创建、管理、销毁等操作由平台自动完成。每个云函数实例都在 [代码]/tmp[代码] 目录下提供了一块 [代码]512MB[代码] 的临时磁盘空间用于处理单次云函数执行过程中的临时文件读写需求,需特别注意的是,这块临时磁盘空间在函数执行完毕后可能被销毁,不应依赖和假设在磁盘空间存储的临时文件会一直存在。如果需要持久化的存储,请使用云存储功能。因此,我们将文件保存在/tmp路径下,文件名随便起,这里我取为exampl.docx。生成文档的代码如下: // Let's generate the Word document into a file: let out = fs.createWriteStream('/tmp/example.docx') // Async call to generate the output file: docx.generate(out) 理论上来说,我们文档生成完毕后,通过fs.ReadFileStream读取文件调用cloud.uploadFile()即可上传到云存储 const fileStream = fs.createReadStream('/tmp/example.docx') return await cloud.uploadFile({ cloudPath: '/tmp/example.docx', fileContent: fileStream, }) 而在测试过程中我发现,云端测试时,云函数调用超时。而后使用本地调试查看问题出在何处。 云函数本地调试的方法不再赘述,看这里即可。https://developers.weixin.qq.com/miniprogram/dev/wxcloud/guide/functions/local-debug.html 通过本地调试,发现cloud.uplodaFile()的网络请求始终时挂起(pending)状态,没有传输数据。 [图片] 经过一天的调试,通过监听文件,发现officegen生成文件完成,执行了我们开头复制粘贴的生成文档后执行的docx.on("finalize",)函数,打印文档生成成功的日志后,仍有文件变动,也就是说,文件并没有生成完毕。这就导致了后续步骤的失败。 当时调试的界面我没有保存,就贴一下fs监听文件的代码吧。 let watcherObj = '/tmp/example.docx' //eventType 可以是 'rename' 或 'change'; 当改名或出现或消失的时候触发rename; recursive:是否监听到内层子目录,默认false; try { let myWatcher = fs.watch(watcherObj,{encoding:'utf8',recursive:true},(event,filename) => { if(event == 'change'){ console.log("触发change事件") } console.log(event) //encoding:文件名编码格式,buffer、默认:utf8等;filename有可能为空 if(filename){ console.log('filename: ' + filename) } }) //change 事件会触发多次 myWatcher.on('change',function(err,filename){ console.log(filename + '发生变化'); }); //50秒后 关闭监视 setTimeout(function(){ myWatcher.close() },5000); } catch (error) { console.log('文件不存在!!') } 为解决这一问题,我最先想到了await,结果发现await对officegen生成文档的接口并不起作用;最终我用了最原始的笨办法:用setTimeout等一会儿再读取文件,大佬们有更好的解决方案还请赐教。 return new Promise((resolve, reject) => { setTimeout(async function () { let data = fs.readFileSync('/tmp/example.docx'); let bufferData = new Buffer.from(data, 'base64'); console.log(bufferData); setTimeout(async function () { resolve(await cloud.uploadFile({ cloudPath: varpath, fileContent: bufferData, })); }, 1000); //等文件再读1秒 }, 6300); //等文件再写一会儿。根据自己的需求调试后确定等待时长,要预留出一定时间确保文档完全生成完毕。 }) //最终返回内容为文件云存储中的CloudID。 四、完整核心代码 const cloud = require('wx-server-sdk') const officegen = require('officegen'); const fs = require('fs'); const docx = officegen('docx'); cloud.init({ env: '这里填入你的云环境' }) // Officegen calling this function after finishing to generate the docx document: docx.on('finalize', async function (written) { console.log('Finish to create a Microsoft Word document.') }) // Officegen calling this function to report errors: docx.on('error', function (err) { console.log(err) }) //save QR saveFile = function (filePath, fileData) { return new Promise((resolve, reject) => { const wstream = fs.createWriteStream(filePath); wstream.on('open', () => { const blockSize = 128; const nbBlocks = Math.ceil(fileData.length / (blockSize)); for (let i = 0; i < nbBlocks; i += 1) { const currentBlock = fileData.slice( blockSize * i, Math.min(blockSize * (i + 1), fileData.length), ); wstream.write(currentBlock); } wstream.end(); }); wstream.on('error', (err) => { reject(err); }); wstream.on('finish', () => { resolve(true); }); }); } // 云函数入口函数 exports.main = async (event, context) => { var time = new Date() var filePath = 'exportVoluntaryData' var fileName = "zyzm" + Date.parse(new Date()) + '.docx' var varpath = filePath + '/' + fileName //get QRcode const result = await cloud.openapi.wxacode.getUnlimited({ page: 'pages/check/check', scene: item._id, width: 240, }) const QRcode = result.buffer await saveFile('/tmp/qr.jpg', QRcode); // Add a Footer: var footer = docx.getFooter().createP(); footer.addText('XXXX证明_' + item._id, { font_size: 10 }); footer = docx.getFooter().createP(); footer.addText(time.toString(), { font_size: 10 }); //下方开始文档每一页的循环 for (var i in item.volunteerInfo) { //标题 let pObj = docx.createP({ align: 'center' }) pObj.addText('XXX证明', { bold: true,XXX font_face: 'KaiTi', font_size: 19, color: '595959' }); //此处省略了一些正文内容 pObj = docx.createP() pObj.options.indentFirstLine = 440; pObj.addText('微信扫描下方小程序码,可核验此证明。', { font_face: 'FangSong', font_size: 12, color: '595959', italic: true, }); pObj = docx.createP() pObj.options.indentFirstLine = 440; pObj.addImage('/tmp/qr.jpg', { cx: 140, cy: 140 }); pObj = docx.createP() pObj = docx.createP({ align: 'right' }) pObj.addText('落款', { font_face: 'FangSong', font_size: 15, color: '595959' }); if (i != ((item.volunteerInfo).length - 1)){ docx.putPageBreak() //分页 } } // Let's generate the Word document into a file: let out = fs.createWriteStream('/tmp/example.docx') // Async call to generate the output file: docx.generate(out) return new Promise((resolve, reject) => { setTimeout(async function () { let data = fs.readFileSync('/tmp/example.docx'); let bufferData = new Buffer.from(data, 'base64'); console.log(bufferData); setTimeout(async function () { resolve(await cloud.uploadFile({ cloudPath: varpath, fileContent: bufferData, })); }, 1000); }, 6300); }) } 本人非计算机相关专业本科生,且本文大部分内容为手打,难免会有差错和疏漏,还请各位指教。 希望本文对你有所帮助。 Soochow University. HaoChen. 2020年2月 ======= 2021-10-15更新 ======= 经过一段时间的使用,上述内容主要存在两点问题:(1)难以判断文件何时生成完毕;(2)连续调用生成文档时,若上一个云函数实例未被销毁,会出现文件内容重复和错乱的问题。 前一段时间进行了更新,因为工作学习忙碌,此次暂不做详解,代码如下。 入口文件index.js// 云函数入口文件 delete require.cache[require.resolve('officegen')]; const cloud = require('wx-server-sdk') var office = require('office.js'); //https://github.com/Ziv-Barber/officegen/blob/master/manual/docx/README.md cloud.init({ env: 'sudaxmt1900' }) const db = cloud.database() const _ = db.command // 云函数入口函数 exports.main = async (event, context) => { return await office.genWord(event); } office.jsconst cloud = require('wx-server-sdk') const fs = require('fs'); function delDir(path) { console.log("delete Dir") let files = []; if (fs.existsSync(path)) { files = fs.readdirSync(path); files.forEach((file, index) => { let curPath = path + "/" + file; if (fs.statSync(curPath).isDirectory()) { delDir(curPath); //递归删除文件夹 } else { fs.unlinkSync(curPath); //删除文件 } }); // fs.rmdirSync(path); // 删除文件夹自身 } } readDocx_fs = function (path) { return new Promise((resolve, reject) => { fs.readFile(path,(err,data)=>{ resolve(data); reject(err); }) }) } //save QR saveFile = function (filePath, fileData) { return new Promise((resolve, reject) => { const wstream = fs.createWriteStream(filePath); wstream.on('open', () => { const blockSize = 128; const nbBlocks = Math.ceil(fileData.length / (blockSize)); for (let i = 0; i < nbBlocks; i += 1) { const currentBlock = fileData.slice( blockSize * i, Math.min(blockSize * (i + 1), fileData.length), ); wstream.write(currentBlock); } wstream.end(); }); wstream.on('error', (err) => { reject(err); }); wstream.on('finish', () => { resolve(true); }); }); } exports.genWord = async (event) => { let officegen = require('officegen'); let fs = require('fs'); let docx = officegen('docx'); //ini delDir('/tmp') var item = event.item var filePath = 'exportVoluntaryData' var fileName = "21zyzm" + Date.parse(new Date()) + '.docx' var varpath = filePath + '/' + fileName //=========以下建构文档内容========== //get QRcode const result = await cloud.openapi.wxacode.getUnlimited({ page: 'pages/check/check', scene: item.id, width: 140, }) const QRcode = result.buffer await saveFile('/tmp/qr.jpg', QRcode); // 这里的fileData是Buffer类型 timeBottom = time.getFullYear() + '年' + (time.getMonth() + 1) + '月' + time.getDate() + '日' for (var i in item.volunteerInfo) { let pObj = docx.createP({ align: 'center' }) pObj = docx.createP({ align: 'center' }) pObj.addText('志愿服务时间证明', { bold: true, font_face: 'KaiTi', font_size: 19, color: '595959' }); pObj = docx.createP() pObj = docx.createP({ align: 'justify' }) pObj.options.indentFirstLine = 440; if (item.volunteerInfo[i].academy && item.volunteerInfo[i].major && item.volunteerInfo[i].grade) { var txt = item.volunteerInfo[i].academy + ' ' + item.volunteerInfo[i].major + '专业 ' + item.volunteerInfo[i].grade + ' ' + item.volunteerInfo[i].name + ' 同学(学号 ' + item.volunteerInfo[i].idnum + '),于 ' + date + '参加 ' + item.title + ' 工作,志愿服务时间达到 ' + item.hours + ' 小时。' } else { var txt = item.volunteerInfo[i].name + ' 同学(学号 ' + item.volunteerInfo[i].idnum + '),于 ' + date + '参加 ' + item.title + ' 工作,志愿服务时间达到 ' + item.hours + ' 小时。' } pObj.addText(txt, { font_face: 'FangSong', font_size: 15, color: '595959' }); pObj = docx.createP() pObj.options.indentFirstLine = 440; pObj.addText('特此证明。', { font_face: 'FangSong', font_size: 15, color: '595959' }); pObj = docx.createP() pObj.options.indentFirstLine = 440; pObj.addText('证明人:' + event.tea_info.name + ' ' + event.tea_info.phone, { font_face: 'FangSong', font_size: 15, color: '595959' }); pObj = docx.createP() pObj = docx.createP() pObj.options.indentFirstLine = 440; pObj.addText('微信扫描下方小程序码,可核验此证明。核验信息与此证明一致时,此证明不加盖公章仍然有效;若不一致,则以加盖公章的证明为准。', { font_face: 'FangSong', font_size: 12, color: '595959', italic: true, }); pObj = docx.createP() pObj.options.indentFirstLine = 440; pObj.addImage('/tmp/qr.jpg', { cx: 140, cy: 140 }); pObj = docx.createP() pObj = docx.createP() pObj = docx.createP({ align: 'right' }) pObj.addText('XXXXX', { font_face: 'FangSong', font_size: 15, color: '595959' }); pObj = docx.createP({ align: 'right' }) pObj.addText(timeBottom, { font_face: 'FangSong', font_size: 15, color: '595959' }); // Add a Footer: pObj = docx.createP() pObj = docx.createP() pObj = docx.createP() pObj.addText('XXXXX证明_' + item._id, { font_face: 'FangSong', font_size: 10, color: '808080' }); pObj = docx.createP() pObj.addText(time.toString(), { font_face: 'FangSong', font_size: 10, color: '808080' }); if (i != ((item.volunteerInfo).length - 1)) { docx.putPageBreak() } } //=======================建构文档内容结束========================= // Let's generate the Word document into a file: let out = fs.createWriteStream('/tmp/' + fileName) return new Promise((resolve, reject) => { docx.generate(out); out.on('close', async function(){ console.log("文件已被关闭,总共写入字节", out.bytesWritten) // console.log('写入的文件路径是'+ out.path); var fileBuf = await readDocx_fs(out.path); var upd = await cloud.uploadFile({ cloudPath: varpath, fileContent: fileBuf, }); console.log(docx) resolve({ event, upd, size: Math.floor(100*out.bytesWritten/1024)/100 + "KB" }) }); out.on('error', (err) => { console.error(err); reject({ errMsg: err }) }); }) }
2021-10-15 - 微信指数小程序出错
[图片] 获取订阅词出错,删除小程序重新进入不行、重启微信打开也不行、重启手机也不行。
2022-02-19 - 实战分享: 小程序云开发玩转订阅消息(二)
[图片]这是实战分享: 小程序云开发玩转订阅消息的第二部分 第一部分链接 《实战分享: 小程序云开发玩转订阅消息(一)》 将订阅消息存入云开发数据库接下来我们创建一个云函数 [代码]subscribe[代码] ,这个云函数的作用是将用户的订阅信息存入云开发数据库的集合 [代码]messages[代码] 中,等待将来需要通知用户时进行调用。 在微信开发者工具的云开发面板中创建数据库集合 [代码]messages[代码] [图片]微信开发者工具新增数据库集合 创建一个 [代码]subscribe[代码] 云函数,在云函数中我们将小程序端发送过来的课程订阅信息,存储在云开发数据库集合中,开发完成后,在微信开发者工具中右键上传并部署云函数。 cloudfunctions/subscribe/index.js [代码]const cloud = require('wx-server-sdk'); cloud.init(); const db = cloud.database(); exports.main = async (event, context) => { try { const {OPENID} = cloud.getWXContext(); // 在云开发数据库中存储用户订阅的课程 const result = await db.collection('messages').add({ data: { touser: OPENID, // 订阅者的openid page: 'index', // 订阅消息卡片点击后会打开小程序的哪个页面 data: event.data, // 订阅消息的数据 templateId: event.templateId, // 订阅消息模板ID done: false, // 消息发送状态设置为 false }, }); return result; } catch (err) { console.log(err); return err; } }; [代码]利用定时触发器来定期发送订阅消息接下来我们需要实现一个定时执行的云函数[代码]send[代码],来检查数据库中是否有需要发送给用户的订阅消息。如果有需要发送的订阅消息,会通过云调用 [代码]cloud.openapi.subscribeMessage.send[代码] 将订阅消息发送给用户。 创建一个名叫 [代码]send[代码] 的云函数,首先要配置云函数,在 [代码]config.json[代码] 的 [代码]permissions[代码] 中新增 [代码]subscribeMessage.send[代码]的云调用权限,然后新增一个 [代码]sendMessagerTimer[代码] 的定时触发器,定时触发器的语法和 [代码]linux[代码] 的 [代码]crontab[代码] 类似,比如,我们配置的 [代码]"0 * * * * * *"[代码] 代表每分钟执行一次云函数。 cloudfunctions/send/config.json [代码]{ "permissions": { "openapi": ["subscribeMessage.send"] }, "triggers": [ { "name": "sendMessagerTimer", "type": "timer", "config": "0 * * * * * *" } ] } [代码]接下来是实现发送订阅消息的云函数,这个云函数会从云开发数据库集合[代码]messages[代码]中查询等待发送的消息列表,检查数据库中是否有需要发送给用户的订阅消息,发送条件可以根据自己的业务实现,比如开课提醒可以根据课程开课日期来检查是否需要发送订阅消息,在我们下面的代码示例里做了简化,筛选条件只检查了状态为未发送。 查询到待发送的消息列表之后,我们会循环消息列表,依次发送每条订阅消息,发送成功后将数据库中消息的状态改为已发送。 cloudfunctions/send/index.js [代码]const cloud = require('wx-server-sdk'); exports.main = async (event, context) => { cloud.init(); const db = cloud.database(); try { // 从云开发数据库中查询等待发送的消息列表 const messages = await db .collection('messages') // 查询条件这里做了简化,只查找了状态为未发送的消息 // 在真正的生产环境,可以根据开课日期等条件筛选应该发送哪些消息 .where({ done: false, }) .get(); // 循环消息列表 const sendPromises = messages.data.map(async message => { try { // 发送订阅消息 await cloud.openapi.subscribeMessage.send({ touser: message.touser, page: message.page, data: message.data, templateId: message.templateId, }); // 发送成功后将消息的状态改为已发送 return db .collection('messages') .doc(message._id) .update({ data: { done: true, }, }); } catch (e) { return e; } }); return Promise.all(sendPromises); } catch (err) { console.log(err); return err; } }; [代码]最终效果 [图片]开课提醒订阅消息截图 源代码https://github.com/binggg/tcb-subscribe-demo[3] 参考资料 [1]注册小程序帐号: https://tencentcloudbase.github.io/2019-09-03-wx-dev-guide-register/ [2]开通云开发服务: https://tencentcloudbase.github.io/2019-09-03-wx-dev-guide-service/ [3]https://github.com/binggg/tcb-subscribe-demo: https://github.com/binggg/tcb-subscribe-demo
2019-10-23 - 这些小程序,举报了一个星期都没结果,为什么我们就违规?
外卖券儿:gh_3c5e3fadb2f3,wxa7946c88e51724e1 外卖探探:gh_6f5e11a547b9,wx19ff73bc91f2c5f0(关联的十个小程序都涉嫌恶意营销) 外卖果果:gh_974a2a99df6e,wxd4643285411b6b2c 券老弟:gh_819517a44f1e,wx6877c80a42702eb3 外卖分销宝:gh_401debbe7d71,wx36592e2ebc5c49db 外卖超省钱:gh_a9e63c9d4531,wxcd0b4b881349caa9 外卖金库:gh_68f14eec067c,wxa127dcc14db86085 外卖助手:gh_be9a3ec90ed1,wxece62eaecee82e45 外卖省赚平台:gh_ee1df53644c9,wx911efd41852f0d64(关联的5个小程序都涉嫌恶意营销) 外卖生活vip:gh_028c00d3593d,wx15c8fc164ebe27fb 外卖组织:gh_d85dfc97114d,wxe7f31da4e9bc039f 外卖省钱酱:gh_52899da77baa,wx441d21e16db44ffc 外卖怎么省:gh_f2360c8f8771,wx8b376ae81b972ffd 以上小程序都存在无主营业务,仅提供电商类小程序商品价格详情展示并导流至相关小程序进行购买等纯导流、无实质内容的运营内容 越靠前的是违规越严重的
2022-01-17 - 关于外卖优惠券类公众号违规行为公告
平台鼓励公众号开发者为用户提供优质的内容和服务。近期,我们发现部分以引导用户领取外卖优惠券为主要业务的公众号(简称为外卖优惠券类公众号)存在违规行为,对用户造成骚扰并引发用户的反感和投诉。 为提升用户体验,自2021年12月9日起,平台将规范此类公众号相关违规行为,并梳理如下几类违规示例供开发者们参考: 1.以任何形式诱导用户点击菜单栏、回复文本内容等: 违规案例一:利用客服消息和群发消息明示或暗示用户点击自定义菜单栏或主动回复文字内容,绕过单一场景的客服消息额度限制,向用户发送外卖营销信息。 [图片] [图片] 违规案例二:临近客服消息额度失效时间,诱导用户再次主动回复文字内容 [图片] 违规案例三:明示或暗示用户点击菜单消息,产生互动。 [图片] 2.在用户无接受服务意愿的情况下,发送营销内容: 违规案例:利用模版消息下发外卖营销信息 [图片] 请开发者自查是否存在以上几类或其他形式的违规行为,并及时整改。一经发现将根据违规程度封禁公众号相关能力直至封号。 如发现恶意对抗行为,将加重处理。 平台会根据现实情况更新规则。
2021-12-22 - 请问这种小程序为什么不封,有关系吗,竞猜?
投诉【appid:wx4692f08fa6ad3bc2】涉嫌违反微信小程序6.1 违反国家法律法规禁止的内容,应该封号处理!这种也能上线吗,线上投诉多次了!无投诉结果,希望官方一视同仁。[图片] 微信公众号推送链接,管理人员可跳转自己体验是不是竞猜:https://mp.weixin.qq.com/s/Xjs0CbDPzzlImpwL1bARyg
2021-09-27 - updatableMessage.createActivityId 为啥只能创建24小时有效期id?
updatableMessage.createActivityId难道我每个活动都必须要在24小时内结束吗? 如果我的活动是连续的24小时私密活动,超出24小时后,其他用户不也可以点进去了吗? 产品经理你设置这个24小时是什么目的?你的逻辑在哪里?
2021-09-06 - 微信小程序优秀的审核员,这也算色情!?
自认为是认真做小程序的开发者。 做了一些表情包相关的小程序。然后接到通知说要整治“儿童软色情”表情包,让自查。 [图片] 按照这篇自查表情包的标准 https://mp.weixin.qq.com/s/gNzUq4tfkO0s6BXISRipew 经过自查,我下架了所有“萌娃”中包含“老公”、“老婆”、“上床”、“洗澡”、“睡觉”的儿童表情包。 我其实本身对于这种表情包被认为是所谓“软色情”这标准不太认可,但是我觉得官方可能也是有苦衷。 这第一波,我自查下架的表情包,举个例子: [图片] 好吧,这是“色情性暗示”,好吧,好吧,好吧,你说是就是~ 但是,没想到即便如此自查,我还是被封了。 [图片] 点开查看详情,看看: [图片] 我尼玛!(此处我的心情就像是👇🏻) [图片] 这红圈里的图也算色情??????离谱吗? 请社区的同学们和领导们看看,我就问一句离谱吗?你就说离还是不离吧? [图片] 看来这个“喜欢”也是性暗示了?那我感觉我已经很难掌握到官方的标准线了 后来我把所有的“萌娃”全下架了,因为我觉得可能审核员也是按照标准审查,这个标准可能很紧张?或者是KPI每天必须下架一些小程序? 然后!!!!! [图片] [图片] 唉,没话说。。。。 [图片] 又来查熊猫头了!牛皮! 好吧,看来“老公”这种性暗示很足的词不行了。 于是我想到了,要不看看微信里官方的搜索表情包里搜出的结果吧?学习一下严格的标准。 [图片] [图片] [图片] 这一套双标,我看行! 然后在试试“搜一搜”里搜“洗澡”,表情包: [图片] 优秀!!! 哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈~ 还真如小肥羊所说,“微信说你低俗你就高雅不起来”。 另外,最后送审核员一个表情包 [图片]
2021-08-12 - 希望官方能够适配这样一种服务端API接口?
可以在服务端通过已授权微信步数的openid用户调用接口获取该用户的微信步数。 既然用户已经同意授权了,我觉得新增通过服务端调用接口获取实时微信步数,会激发出更多新的创意产品。
2021-06-21 - 如何使用云开发实现发送邮件功能
新手:因为是刚接触云开发,所以有说的不对的地方,大佬帮忙指正。 背景:摆脱每周五晚写周报的烦恼。 目标:本次小目标就先实现小程序云函数发送邮件功能。 终极目标,写一个todolist小程序,每周五汇总本周工作,下周工作,使用云函数定时器触发,定时发送邮件。 [图片] 首先给大家介绍一个发邮件的第三方模块,nodemailer Nodemailer Github地址 Nodemailer官方文档 找到官方文档最下面,有一串示例代码。这次我们要用到的就是它了。 使用nodemailer我们需要使用到的传输方式:SMTP 文档如下:https://nodemailer.com/smtp/ 这里有介绍如何使用,示例代码我就不给大家复制过来了。 何为SMTP? 这里给大家百度了一发: SMTP是一种提供可靠且有效的电子邮件传输的协议。SMTP是建立在FTP文件传输服务上的一种邮件服务,主要用于系统之间的邮件信息传递,并提供有关来信的通知。SMTP独立于特定的传输子系统,且只需要可靠有序的数据流信道支持,SMTP的重要特性之一是其能跨越网络传输邮件,即“SMTP邮件中继”。使用SMTP,可实现相同网络处理进程之间的邮件传输,也可通过中继器或网关实现某处理进程与其他网络之间的邮件传输。 [图片] 怎么开启SMTP服务? 自己搭建邮件服务器是非常麻烦的,我们可以借助于QQ邮箱、Gmail、163个人邮件系统或企业邮件系统开启SMTP服务,SMTP也就是简单邮件传输协议,通过它可以控制邮件的中转方式,帮助计算机在发送或中转信件时找到下一个目的地,也就是发送邮件。 不同的邮件系统有着不同的smtp发送邮件服务器,端口号也会有所不同。 我这边以QQ邮箱为示例操作下。 在QQ邮箱的设置>账户 [图片] [图片] 我们可以看到IMAP/SMTP的开启和关闭,这里我们先开启,然后你会得到一串授权码,保存下这个授权码,我们后续会用到。QQ邮箱的发送邮件服务器:smtp.qq.com,使用SSL,端口号465或587。 [图片] 使用云函数发送邮件 首先我们创建一个云函数,比如就叫sendMail 吧。在该云函数的package.json里添加如下代码。右键点击sendMail云函数,在终端中打开,或者本地终端cd到该云函数文件夹内也可。输入npm install 安装nodemailer最新版依赖。 [代码] "dependencies": { "wx-server-sdk": "~2.1.2", "nodemailer": "latest" } [代码] 然后在index.js中添加如下代码: [代码]const cloud = require('wx-server-sdk') cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV, }) exports.main = async (event) => { const nodemailer = require("nodemailer"); let transporter = nodemailer.createTransport({ host: "smtp.qq.com", //SMTP服务器地址 port: 465, //端口号,通常为465,587,25,不同的邮件客户端端口号可能不一样 secure: true, //如果端口是465,就为true;如果是587、25,就填false auth: { user: "8662054@qq.com", //这是我的邮箱账号,改为你们的即可。 pass: "myukmlybjotjbghh" //这里是我们上面说要用到的授权码,不是QQ邮箱的密码 } }); let postMsg = { from: event.from, //发件邮箱 to:event.to, //收件人 subject: event.subject, text: event.text, html: event.html // text 和 html只能同时存在一个。 }; let res = await transporter.sendMail(postMsg); return res; } [代码] 右键部署上传云函数。 到这里我们的发送邮件云函数就完成啦。 [图片] 如何调用发送邮件云函数? 调用方式也很简单: [代码]<input type="text" placeholder="请输入收件人邮件地址" bindinput="handleToAddress"/> <input type="text" placeholder="请输入邮件主题" bindinput="handleSubject"/> <textarea placeholder="请输入邮件内容" auto-height bindinput="handleTextarea" /> <button bindtap="handleClick">发送邮件</button> [代码] [图片] 这里因为我们绑定了 发件人是我的固定邮箱,所以没有增加发送人的输入框。 textarea 我有尝试使用editor组件生成富文本进行操作,最后发现弄出来样式太龊,就干掉了。 [代码]对了差点忘了: 邮件是支持: html:富文本 cc: 支持 抄送 bcc: 支持 密送 attachments: 支持多种附件形式,可以是String, Buffer或Stream 目前我这边用不到,就不展示了,有需要的可查看官方文档,或者评论给你示例代码。 [代码] 至于我们调用的地方: 首先拿到输入的值,然后调用云函数发送。 [代码]Page({ data: { }, handleToAddress(e) { this.setData({ toAddress: e.detail.value }) }, handleSubject(e) { this.setData({ toSubject: e.detail.value }) }, handleTextarea(e) { this.setData({ toTextArea: e.detail.value }) }, handleClick() { wx.showLoading({ title: '发送中' }) const {toAddress, toSubject, toTextArea} = this.data wx.cloud.callFunction({ name: 'sendMail', data: { from: '8662054@qq.com', to: toAddress, subject: toSubject, text: toTextArea // html: '<p><b>你好:</b><img src=""></p>' +'<p>欢迎欢迎<br/></p>' }, success: res => { wx.hideLoading() console.log(res) wx.showToast({ title: '发送成功' }) }, fail: err =>{ wx.hideLoading() console.log(err) } }) } }) [代码] 实现效果图 [图片] [图片] [图片] 以上就完成了使用云开发实现发送邮件功能。 有不懂的欢迎评论留言。 老规矩结尾的代码片段 没了 😂 。 代码仓库地址:https://github.com/minchangyong/wx-cloud-demo 最后感谢李东bbsky 大佬指导。
2020-07-09 - 云开发云函数中使用Redis的最佳实践,包括五种常用数据结构和分布式全局锁
Redis因其拥有丰富的数据结构、基于单线程模型可以实现简易的分布式锁、单分片5w+ ops的超强性能等等特点,成为了大家处理高并发问题的最常用的缓存中间件。 那么云开发能不能使用Redis呢?答案是肯定的。 下面我介绍下云开发中Redis使用的最佳实践: 第一步、购买Redis,安装Redis扩展 参见官方文档:https://developers.weixin.qq.com/community/develop/article/doc/000a4446518488b6002c9fa3651813 吐槽一下,写这篇文章的原因之一就是上面的官方文档中的示例代码是在不堪入目,希望这篇文章能让小伙伴少踩些坑。 第二步、创建并部署测试云函数,配置云函数的网络环境 [图片] 第三步、编写代码 cache.js const Redis = require('ioredis') const redis = new Redis({ port: 6379, host: '1.1.1.1', family: 4, password: 'password', db: 0 }) exports.redis = redis /** * 加redis全局锁 * @param {锁的key} lockKey * @param {锁的值} lockValue * @param {持续时间,单位s} duration */ exports.lock = async function(lockKey, lockValue, duration) { const lockSuccess = await redis.set(lockKey, lockValue, 'EX', duration, 'NX') if (lockSuccess) { return true } else { return false } } /** * 解redis全局锁 * @param {锁的key} lockKey * @param {锁的值} lockValue */ exports.unlock = async function (lockKey, lockValue) { const existValue = await redis.get(lockKey) if (existValue == lockValue) { await redis.del(lockKey) } } 上面是操作redis的工具方法,可以打包放到云函数的层管理中,方便其他云函数引用。层管理使用方式参见官方文档:https://cloud.tencent.com/document/product/876/50940 index.js const cloud = require("wx-server-sdk") const cache = require('/opt/utils/cache.js') // 使用到了云函数的层管理 cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) global.cloud = cloud global.db = cloud.database() global._ = db.command global.$ = _.aggregate exports.main = async (event, context) => { context.callbackWaitsForEmptyEventLoop = false const wxContext = cloud.getWXContext() let appId = wxContext.APPID if (wxContext.FROM_APPID) { appId = wxContext.FROM_APPID } let unionId = wxContext.UNIONID if (wxContext.FROM_UNIONID) { unionId = wxContext.FROM_UNIONID } let openId = wxContext.OPENID if (wxContext.FROM_OPENID) { openId = wxContext.FROM_OPENID } // redis五种常用数据结构 // 字符串 await cache.redis.set('hello', 'world') // 无过期时间 await cache.redis.set('hello', 'world', 'EX', 60) // 过期时间60s let stringValue = await cache.redis.get('hello') console.log('string: ', stringValue) // hash await cache.redis.hset('hash', 'hello', 'world') let hashValue = await cache.redis.hget('hash', 'hello') console.log('hash: ', hashValue) // list await cache.redis.lpush('list', 'hello', 'world') let listList = await cache.redis.lrange('list', 0, -1) // 读取队列所有元素 await cache.redis.ltrim('list', 1, 0) // 清空队列 console.log('listList: ', listList) // set await cache.redis.sadd('set', 'hello', 'world') let setExist = await cache.redis.sismember('set', 'hello') // 检查元素是否在集合中 console.log('set: ', setExist) // zset await cache.redis.zadd('zset', 1, 'hello', 2, 'world') let zsetList = await cache.redis.zrange('zset', 0, -1, 'WITHSCORES') console.log('zsetList: ', zsetList) // redis实现分布式全局锁 // 加全局锁,锁的过期时间应根据实际业务调整 const createOrderLock = `createOrderLock:${unionId}` const ts = Date.now() if (!(await cache.lock(createOrderLock, ts, 3))) { return { code: 4, msg: '操作太频繁了' } } // 这边写全局互斥的业务逻辑代码 // 比如创建订单,一个用户同时只能并发创建一个订单 // 解全局锁 await cache.unlock(createOrderLock, ts) return { code: 0, data: {} } } 上面是测试云函数的入口文件,演示了redis五种常用数据结构和redis全局锁的使用方法。 最后还有个小tips,所有引用到cache.js的云函数需要安装ioredis的依赖,进入云函数目录,使用如下命令: npm install ioredis
2021-06-17 - UNI-APP使用云开发跨全端开发实战讲解
UNI-APP 是一个使用 Vue.js 开发所有前端应用的框架,开发者编写一套代码,可发布到iOS、Android、Web(响应式)、以及各种小程序(微信/支付宝/百度/头条/QQ/钉钉/淘宝)、快应用等多个平台。 本文为大家讲解如何采用云开发官方JS-SDK,接入云开发后端服务并支持UNI-APP全部端(不止于微信小程序) JS-SDK和UNI-APP适配器1.JS-SDK和适配器云开发官方提供的@cloudbase/js-sdk,主要用来做常规WEB、H5等应用(浏览器运行)的云开发资源调用,也是目前最为完善的客户端SDK。 目前市面上大部分的轻应用、小程序包括移动应用APP都是采用JS来作为开发语言的,所以我们可以对TA进行轻微改造,就可以轻松使用在各种平台中。 但是单独改造SDK包会有些许风险,比如在原SDK包升级时需要重新构造,就造成了无穷无尽的麻烦,改造成本相当大。 官方的产品小哥哥深知这种不适和痛苦,所以在@cloudbase/js-sdk 中提供一套完整的适配扩展方案,遵循此方案规范可开发对应平台的适配器,然后搭配 @cloudbase/js-sdk 和适配器实现平台的兼容性。 不了解的小伙伴肯定会有些茫然,我来用浅显的语言解释一下,就是@cloudbase/js-sdk 将底层的网络请求以及相关基础需求以接口的形式暴露出来,我们按照平台的特殊API来补充这些接口,sdk就可以根据这些补充的接口,无障碍的运行在平台中了。 如果我们想在UNI-APP中使用@cloudbase/js-sdk ,底层网络请求你需要来补充,因为sdk原本是适应浏览器的,TA不知道UNI-APP怎么对外发请求,所以你需要将uni.request 方法补充到TA暴露的接口中。补充完毕后,@cloudbase/js-sdk 就可以在UNI-APP中活泼的运行了。 我们将所有的uni方法全部补充到JS-SDK暴漏的接口中去,就形成了一个完整的适配器,我们将其成为uni-app适配器。 2.UNI-APP适配器UNI-APP的整体接口都是公开透明的,我们在开发UNI-APP时也都遵照同一套接口标准。所以小编已经将uni-app适配器制作完毕,大家只需要在使用时接入适配器就可以了。 我们在项目目录main.js中引入云开发JS-SDK,然后接入我们的UNI-APP适配器即可。 import cloudbase from '@cloudbase/js-sdk' import adapter from 'uni-app/adapter.js' cloudbase.useAdapters(adapter); cloudbase.init({ env: '',//云开发环境ID appSign: '',//凭证描述 appSecret: { appAccessKeyId: 1,//凭证版本 appAccessKey: ''//凭证 } }) 移动应用登录凭证云开发SDK在使用过程中,向云开发服务系统发送的请求都会需要验证请求来源的合法性。 我们常规 Web 通过验证安全域名,而由于 UNI-APP 并没有域名的概念,所以需要借助安全应用凭证区分请求来源是否合法。 登录云开发 CloudBase 控制台,在安全配置页面中的移动应用安全来源一栏:[图片] 点击“添加应用”按钮,输入应用标识:uni-app(也可以输入其他有标志性的名称),需要注意应用标识必须是能够标记应用唯一性的信息,比如微信小程序的 appId 、移动应用的包名等。[图片] 添加成功后会创建一个安全应用的信息,如下图所示:[图片] 我们需要保存一下上图中的版本(示例为1)、应用标识(示例为uni-app)、以及点击获取到的凭证(示例为demosecret) 在项目目录中,我们将main.js中的init部分补全 import cloudbase from '@cloudbase/js-sdk' import adapter from 'uni-app/adapter.js' cloudbase.useAdapters(adapter); cloudbase.init({ env: 'envid',//云开发环境ID,保证与你操作登录凭证一致 appSign: 'uni-app',//凭证描述 appSecret: { appAccessKeyId: 1,//凭证版本 appAccessKey: 'demosecret'//凭证 } }) 如此,你就可以正常的进行云开发的登录使用了。 需要注意以下4点: 你需要设置uni-app的各端安全域名为:request:tcb-api.tencentcloudapi.com、uploadFile:cos.ap-shanghai.myqcloud.com、download:按不同地域配置使用此种方法接入云开发是全端支持,并不会享有微信小程序生态的一些便利,微信小程序开发还是需要依赖正常请求调用过程(将云开发作为服务器来对待),但你可以判断wx来使用wx.cloud来兼容。使用云开发的匿名登录时,受各端实际情况影响,可能不能作为常久唯一登录id,需要根据自身业务建立统一账户体系,具体可使用自定义登录来进行。UNI-APP支持WEB网页端上线时,需要将网页域名配置到云开发安全域名中(防止WEB下载文件导致跨域)示例代码详解示例项目中已经基本构建了uni-app使用云开发的各种流程代码。 在页面中进行匿名登录: // index.vue import cloudbase from '@cloudbase/js-sdk' export default { data() { return { title: '登录中' } }, onLoad() { cloudbase.auth().anonymousAuthProvider().signIn().then(res => { this.title = '匿名登录成功' }).catch(err => { console.error(err) }) } } 调用云函数并收到返回结果: import cloudbase from '@cloudbase/js-sdk' export default { methods: { call: function() { cloudbase.callFunction({ name: "test", data: { a: 1 } }).then((res) => { console.log(res) }); } } } 操作数据库: import cloudbase from '@cloudbase/js-sdk' export default { methods: { database: function() { cloudbase.database().collection('test').get().then(res => { console.log(res) }) } } } 实时数据库监听: import cloudbase from '@cloudbase/js-sdk' export default { methods: { socket: function() { let ref = cloudbase.database().collection('test').where({}).watch({ onChange: (snapshot) => { console.log("收到snapshot", snapshot); }, onError: (error) => { console.log("收到error", error); } }); } } } 上传文件(框架限制,WEB端无法操作): import cloudbase from '@cloudbase/js-sdk' export default { methods: { upload: function() { uni.chooseImage({ count: 1, sizeType: ['original', 'compressed'], sourceType: ['album'], success: function(res) { console.log(res.tempFilePaths[0]) cloudbase.uploadFile({ cloudPath: "test-admin.png", filePath: res.tempFilePaths[0], onUploadProgress: function(progressEvent) { console.log(progressEvent); var percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total); } }).then((result) => { console.log(result) }); } }); } } } 下载文件(需要注意地域域名,配置安全域名): import cloudbase from '@cloudbase/js-sdk' export default { methods: { download: function() { cloudbase.downloadFile({ fileID: "cloud://demo-env-1293829/test-admin.png" }).then((res) => { console.log(res) }); } } } 部署步骤将项目下载后使用HBuilderX打开。按照获取移动安全凭证的指引,填写至mian.js相应处。打开目录命令行,npm i执行安装依赖。打开云开发控制台,开启匿名登录。新建一个默认的云函数,名称为test(逻辑内容直接返回event即可)新建一个数据库,名称为test(随便添加几个记录,设置权限为所有人可读)调整项目pages/index/index.vue中,21行代码,在登录成功后调用相应函数。以下是WEB端运行时展示:[图片] 关于uni-app适配器在util/adapter中,只进行了简单的测试,保证可用性,后续请关注官网获取最新适配器依赖此方法有别与uniCloud,是直接使用uni请求底层,依赖官方JS-SDK进行云开发服务的交互处理,在使用时注意区别。项目地址:https://github.com/AceZCY/UNI-for-CloudBase
2020-12-08 - 向小程序客服发送小程序卡片 服务器收不到消息通知?
小程序->客服消息->接收消息和事件接口,前端页面向客服消息推送了小程序卡片,后台服务器未收到【小程序卡片消息】推送,而文本和图片消息,后台服务器可正常收到消息推送。
2020-10-21 - 推荐官介绍
推荐官是什么 推荐官是一个招募客户/粉丝/等有意向为本店推广商品人员的扩圈销售功能。 通过设置并发布招募令,设置梯级晋升的佣金激励,商家可募集到愿意为本店带货的推荐官。 商家可设置给推荐官的定向推广货源,每个商品支持单独设置佣金模式。 带货的推荐官将会拥有一个小商店,可以直接从你的店选择推广商品添加到他的店展示,也可以通过分享链接或海报的方式推广,为你的店带来流量转化。 [图片] 推荐官的应用场景 招募特定粉丝/购买过客户,促进有意向的客户为自己推广宣传长期与达人合作,定向招募达人成为自己的推荐官区域连锁店,每个店均可成为总店的推荐官,总店统一管理推广商品,激励销售与推广机构合作,分渠道设置推广人员招募令,查看招募数据 推荐官核心功能 (一)招募令 可设置多个,每个均有招募数据统计,方便分渠道招募、衡量效果可将招募令显示在自己的小商店中,定向招募客户支持多种分享方式,方便传播(二)推荐官的独立店 每个招募来的推荐官将会拥有一个小商店,支持关联视频号推荐官可以从关联的不同企业小商店中选品佣金将会自动结算到推荐官的店铺超管的微信零钱账号(三)定向货源,梯度佣金 可设置仅对所有推荐官展示的推广商品库按照销售金额,可梯度设置推荐官佣金,激励高销售额推荐官 推荐官与导购分销和小商店带货中心(小程序联盟)的区别 导购分销的导购们为店铺的成员,且不拥有自己的小商店,适合员工场景。推荐官更适合招募非员工的推广人员。 推荐官和导购可以一起管理,统一设置推广商品,查看推广人的销售数据。 小商店带货中心适用于商家需要通过平台找到更多推广者, 有商品入驻标准与基础佣金设定规则。 推荐官功能详细介绍 1、设置推广商品 选品 从小商店已添加的上架商品中,选取商品作为推广商品 推广商品库中的商品产生变化中,推荐官的选品面板里看到的商品也会实时产生变化。 [图片] 设置佣金 设置商品的提成模式及对应比例 支持批量设置 每个商品支持2种模式(二选一) 按等级比例:该商品的提成比例将由推广人坐在的等级提成比例决定。(等级比例商家自己设置)按固定比例:固定设置一个比例,提成将会按照商品的实际支付价格乘以这个比例计算。[图片] 2、设置招募令 编辑 商家可以自由编辑招募令的模版 支持填写推广优势,设置显示高佣商品及对应佣金,达到提高招募令吸引效果的作用。 商家也可按照自身需要,选择展示已有推荐官的排行版,展示独家资质。 [图片] 分享 发布成功的招募令,支持分享传播 目前支持的分享方式: 链接: 分享的短链点击后可直接打开小程序页面,适合联合一段文案一起宣传转发小程序码:招募令页面的小程序码,用户扫码后将会直接打开招募令页面,商家可用于设计招募海报加入该码。[图片] 管理 点我进入小商店交流群-对接商家和推客/推荐官 [图片] 一个商家可同时发布多个招募令。 发布后的招募令,支持查看招募数据。 备注:发布后的招募令,若要重新编辑,需下线后才可编辑,编辑完成后,需要重新发布 [图片] 3、申请成为推荐官 查看招募令 [图片] 申请成为推荐官 若该申请者满足商家设置的条件(如,是否有购买本店商品) 用户点击申请,若该用户已经有一个小商店,则立即会成为本店推荐官。 将会引导用户去小商店助手上架选品本店的推广商品。 [图片] 若该用户申请推荐官时还未曾拥有一个小商店,将会引导用户创建小商店开始推荐商品。 [图片] [图片] 4、推荐官选品推荐、分享 推荐官可以进入下商店的移动端管理后台,在选品渠道中,选择推荐官进入 [图片] 进入后将会看到当前所产生推荐关系的所有店铺,可选择店铺的推广商品选品。 [图片] [图片] 5、佣金结算及查看 售后期结束(用户确认收货后27天), 将结算佣金。发生退款,佣金将不结算。 结算的佣金将直接打款至推荐官的微信零钱。 推荐官也可在带货中心-带货收入的部分,选择「推荐官」渠道,查看对应的每笔带货收入及当前打款进度。 [图片] [图片] 常见问题 (一)什么情况下, 推荐官推荐的商品会失效(推荐官的管理端和它小商店中已上架的对应商品将会直接被下架) A 取消某个商品的推广 1,商家在pc后台将商品从推广商品库中删除, 2. A端,我带的货,取消带货,C端下架 B 推荐官不再是本店推荐官 推荐官主动选择,不再推荐本店的商品(在我推荐的店中管理),a端推荐商品页和c端已上架的商品要下架 商家在PC端操作,移除了该推荐官 (二)什么方式的推荐,会结算佣金给推荐官? 点此跳转:详细结算规则链接 直接购买: 通过推荐官小商店的商品列表,进入推荐官添加的商品详情页,购买当前推荐商品 分享商品详情页购买: 用户A进入小商店的商品列表,点击进入推荐上架的商品详情,分享商品给别的用户B,用户B下单 链接分享: 推荐官从带货中心-我带的货,通过分享(非上架)方式,以海报/链接的形式,分享商品后,用户下单 (三)推荐官可以同时为多个店带货吗? 可以。 若有多个店同时招募了一个推荐官,该推荐官可以同时从多个店中选货推广。1个推荐官的店最多可以绑定10个企业小商店。 (四)一个店最多有多少个推荐官 一个小商店(支持企业/个体/标准版组件),最多招募10万个推荐官(同时)。 点我进入小商店交流群-对接商家和推客/推荐官 [图片]
2021-06-23 - 珊瑚安全今天之后不支持新购了,云开发还有啥更好的内容安全检测方案吗?
目前使用云开发较多,检测图片安全的坑: 1、图片宽高乘积大小问题(通过压缩解决) 2、云函数传输数据大小限制问题(压缩解决) 3、云函数调用那个速度慢得像蜗牛 你们还有什么更好的解决方案吗?
2021-04-09 - 小程序云开发--多个小程序协同工作
概述:小程序云开发提供给开发者可以无需搭建服务器,即可使用云端能力来开发微信小程序、小游戏。云开发为开发者提供完整的原生云端支持和微信服务支持,弱化后端和运维概念,无需搭建服务器,使用平台提供的 API 进行核心业务开发,即可实现快速上线和迭代,可以说这个能力可以极大的提高一个前端开发的单兵作战能力。因为个人爱好,闲暇时间也用云开发去开发过几个小程序,作为一个简单系统包括了后台管理小程序和用户使用的小程序,那么问题来了,数据库和云函数只能对应在一个小程序,而且存储在云端文件系统里的图片的访问也只能在当前的小程序中访问,对于另一个小程序怎么去访问数据、访问图片呢? 一、从小程序B访问小程序A的云函数 这里主要利用了云开发提供的通过http接口访问云函数的能力,文档链接 1、开通小程序A的云开发权限 & 新建集合 由于文章篇幅以及内容重点原因,创建小程序、开通云开发权限我们先略过了,我们首先创建一个集合”users“(数据库),设置它的权限为“所有用户可读,仅创建者可写”,这里是为了将来可以通过云函数进行数据的插入,权限链接。 2、新建小程序A的云函数 这个步骤相对简单,在这里我们假设创建了一个"users_add"的一个添加用户的云函数,作用是用于添加用户。主要逻辑是首先判断用户是否已经存在,若存在则返回“已存在”,若不存在则添加用户并返回“成功”。代码完成之后云函数上传并部署。直接上代码吧~ // 云函数入口文件 const cloud = require('wx-server-sdk') cloud.init() const db = cloud.database() const _users = db.collection('users') // 云函数入口函数 exports.main = async (event, context) => { const user = await _users.where({ openid: event.openid }).get().catch((err) => err); if (!user || user.errMsg != 'collection.get:ok' || user.data.length != 0) { return { errcode: -1001, errmsg: "新增失败" } }else if (user.data.length != 0) { return { errcode: -2001, errmsg: "用户已存在" } } const res = await _users.add({ data: { name: event.name, age: event.age, sex: event.sex, tel: event.tel, openid: event.openid, createtime: new Date().getTime(), } }) if (res && res._id != '') { return { errcode: 0, errmsg: 'success', data: { res: res } } }else{ return { errcode: -9999, errmsg: "新增失败", data: { res: res } } } } 3、小程序B调用小程序A的云函数思路 还是一样创建小程序、开通云开发权限我们先略过了。通过官方文档的说明我们知道,通过http的方式可以访问我们的云函数,官方给予我们的示例是这个样子的:POST https://api.weixin.qq.com/tcb/invokecloudfunction?access_token=ACCESS_TOKEN&env=ENV&name=FUNCTION_NAME,那么我们需要的获取的参数有:access_token(接口调用凭证,获取的文档可以点击此处) 、env(云开发环境ID)、name(云函数名称),这里的access_token比较烦,需要先获取,时效为2小时,官方文档建议我们有个中控服务统一获取和刷新。另外,我们每次调用A小程序的云函数都直接在小程序B里去发这个请求的话,每次都要重复获取token和填写env、name参数,这不符合我们的设计模式。那么还是老办法,我们创建一个云函数,每次小程序B里需要调小程序A的云函数时,先经过中间层即B自己的工具云函数,将方法名称和参数传给该工具云函数,由其完成token的获取、填写env、调小程序A的云函数,将返回的数据返回给小程序。 4、开通小程序B的云开发权限 & 新建集合 & 新建工具云函数 基于此思路我们首先创建一个集合用来记录调用接口凭证的集合"token",并设置其权限;然后新建一个云函数"api",代码如下~ // 云函数入口文件 const cloud = require('wx-server-sdk') cloud.init() const db = cloud.database() const _token = db.collection('token') const _ = db.command; var rp = require('request-promise') // 云函数入口函数 exports.main = async (event, context) => { const wxContext = cloud.getWXContext() // 获取数据库集合中的“token” const token = await _token.get().catch((err) => err); var access_token = ''; // token不存在、token过期的情况下,重新获取token if (!token || token.errMsg != 'collection.get:ok' || token.data.length == 0 || token.data[0].expires_in < new Date().getTime()) { //删除token里保存的数据 await _token.where({ expires_in: _.gt(0) }).remove() // 获取access_token,真实情况下请替换自己的appid和secret var res = await rp("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=wxb1c2c7******0759&secret=9840548c1348*****aa91bc74ae94") .then(function (res) { return res }) .catch(function (err) { return { errcode: -1, errmsg: "获取access_token失败" } }); res = JSON.parse(res); // 将access_token插入到token表中缓存起来 await _token.add({ data: { access_token: res.access_token, expires_in: res.expires_in * 1000 + new Date().getTime() } }) access_token = res.access_token } else {//token存在且未过期,直接取出来使用 access_token = token.data[0].access_token } // 调小程序A的云函数的参数 var options = { method: 'POST', uri: `https://api.weixin.qq.com/tcb/invokecloudfunction?access_token=${access_token}&env=prod-en**h&name=${event.func}`,// 注意env参数,请替换自己的环境id body: event.data, json: true }; options.body.openid = wxContext.OPENID// 加上当前用户的openid var responseData = await rp(options).then(res => res); if (responseData.errcode == 0) { return { errcode: 0, errmsg: 'success', data: JSON.parse(responseData.resp_data) } } else { return { errcode: -3001, errmsg: '接口调用失败', data: responseData && responseData.resp_data ? JSON.parse(responseData.resp_data) : {} } } } 5、小程序B页面中调小程序A的云函数 这个就相对简单了,直接看代码吧~ wx.cloud.callFunction({ name: 'api',// 小程序B自己的云函数 data: { func: 'users_add',// 小程序A的云函数 data: {// 小程序A的云函数需要传递的参数 name: this.data.name, age: this.data.age, sex: this.data.sex, tel: this.data.tel } } }).then(res => { if (res && res.result && res.result.data && res.result.data.errcode == 0) { wx.showToast({ icon: 'none', title: '添加成功', duration: 2000 }) }else{ wx.showToast({ icon: 'none', title: '添加失败', duration: 2000 }) } }) 二、从小程序B访问小程序A上传的图片或文件 一开始的时候是直接使用了小程序A云开发自带的文件上传,文档可以看这里,上传之后返回的是文件id,如cloud://xxx.png,在小程序A里访问是没有问题的,但是在小程序B里访问时就打不开了。。。当然我们可以使用官方提供的方法去换取临时链接(有效期只有2个小时,哎......),想了想每次小程序B去访问A上传的图片还得去转链接,另外小程序B上传的图片怎么储存在小程序A的云数据库里呢?尤其是像上传到同一个集合中的同一个字段名,还得去记录是哪个小程序上传的,每次访问时都得需要换取临时链接,太尴尬了。。。所以,个人推荐使用的是第三方的文件存储服务器,生成一个永久链接,直接保存在数据库中,不管哪个小程序访问都可以直接打开。 三、总结 正如概述中所说的小程序云开发给予开发人员极强的单兵作战能力,我们只需关注业务开发而无需去投入过多的精力关注数据库和运维的内容,开发起来快速且流畅。本文记录里一下本人开发过程中碰到的一些问题,分享出来跟大家一起交流学习,若大家由更好的方法,请联系我,我们一起探讨探讨 : ) ps:云存储的文件能开放个功能让我们生成永久链接该多爽。。。还能当个文件服务器使用,哈哈哈哈~
2020-08-19 - 这完全像素级抄袭小程序呀?
我的小程序:【外链助手】 对方小程序:【智能外链】 这个小程序玩法和UI,我是100%代码、UI、素材原创,也是全网第一家发布的。 这就不江湖了吧,你可以做成其他任何style,但是别像素级抄袭我呀,哥们~ 你连我的素材和公众号文章都抄袭我的。。。。。。。 [图片][图片]
2021-01-11 - 微信小程序PC端功能建议
现在微信支持PC能力会越来越好,那么为了用户能有更好的体验,我建议新增功能如下: 用户点击小程序右上角三个点之后或者直接提供一个button的type,底部弹出的框中新增一个选项: “电脑上打开小程序” 用户点击这个按钮后, 1、微信PC客户端如果未登录,手机端显示:请先登录PC端微信 2、如果已经登录,那么PC端直接蹦出该小程序,并且直达手机端正在操作的页面 用户点击按钮在PC端跳转这个API,最好开发者可配置页面和参数,以更方便同步页面数据和状态。
2020-07-05 - 小程序头像是否可再分配一次修改的机会?
APPID:wx78a41dd32a61e6e7 官方大大,小程序现在还未上线,但是小程序头像可修改次数已达上限,但是想再次修改一次头像! 所以麻烦官方大大能够再给一次修改机会。
2020-06-29 - 输入法遮挡浮动层笔记1
1.预期效果点击输入获取焦点时触发,浮动层在输入法之上 [图片] 2.当失去焦点时隐藏输入法浮动层在底部 [图片] 3.css 代码 .layer{ border-top: solid 1px#e5e5e5; position: fixed; bottom: ; left: ; z-index: 999; background: #fff; width: 100%; } 4.js实现方式,输入框获取焦点时,获取高度,然后值值给fixed 浮动层 底部距离 [图片] data{ lofocus// 默认为0 }, // 获取焦点时触发 focusfunction{ console.log(e); this.setData({ lofocus: e.detail.height }) }, // 失去焦点时触发 blurfunction(){ this.setData({ lofocus: }) }, <!--页面view 代码 padding-bottom 为兼容 苹果x 等机型 -->viewclass"layer" style"padding-bottom:{{lofocus>0?'0':'constant(safe-area-inset-bottom)'}}; padding-bottom:{{lofocus>0?'0':'env(safe-area-inset-bottom)'}} ;bottom:{{lofocus}}px;" 我是浮动层 </view
2020-01-07