一个研发是如何一步一步把一个小需求越搞越大的
通过一个小小的需求来玩玩腾讯云的云开发
前言
我有一个同事。
他叫小草。
是个开发,男的。
需求背景
小草呢,最近在搞一个开源小项目。
这两天找到我,”小丁啊,给我加个小功能。简单!“
需求:页面上加几个输入框,一个输入框填一个邮箱地址,用户提交后,给这个邮箱发个邮件。
开始整活
”小草啊,这个需求简单,给我一个接口,我一个切图仔唰唰唰切一个页面,完了调它一个接口,齐活。“
切图仔,切页面
小草的这个项目啊,是一个nuxt的项目,UI框架用的ant-design。nuxt嘛,大家知道,跟vue差不多的写法,这简单啊,有手就行。开搞开搞。
首先,写个界面出来。
- <a-form-model
- :labelCol="{ span: 2, offset: 0 }"
- :wrapperCol="{ span: 16, offset: 0 }"
- ref="refForm"
- :model="emailModel"
- :rules="rules"
- v-if="showForm"
- >
- <a-form-model-item ref="name" label="您的姓名:" prop="name">
- <a-input size="large" v-model="emailModel.name" />
- </a-form-model-item>
- <a-form-model-item ref="email" label="您的邮箱:" name="email" prop="email">
- <a-input size="large" v-model="emailModel.email" />
- </a-form-model-item>
- <a-form-model-item ref="content" label="您的内容:" prop="content">
- <a-textarea v-model="emailModel.content" placeholder="邮件内容啊" :rows="4" />
- </a-form-model-item>
- <a-form-model-item :wrapper-col="{ span: 2, offset: 2 }">
- <a-button size="large" type="primary" @click="onSubmit" :loading="loading">发送</a-button>
- </a-form-model-item>
- </a-form-model>
然后,写点 JS 吧。
- /***********data里的数据啊***************/
- data() {
- return {
- loading: false,
- showForm: true,
- rules: {
- content: [{ required: true, message: '随便填点什么吧', trigger: 'blur' }],
- email: [
- { required: true, message: '邮箱还是要必填的', trigger: 'blur' },
- { type: 'email', message: '邮箱格式注意一下', trigger: 'blur' },
- ],
- },
- emailModel: {
- name: '',
- email: '',
- content: '',
- },
- };
- },
-
- /***********写个函数啊***************/
- methods: {
- onSubmit() {
- this.$refs.refForm.validate((valid) => {
- if (valid) {
- this.loading = true;
- // 开始调接口拉
- } else {
- console.log('error submit!!');
- return false;
- }
- });
- },
- }
搞定,去找小草要接口去。
”小草,接口给我,联调了!“
”接口?什么接口!“
”这点小功能还需要专门出接口?你是不是不行?”
“!!!!!@@##1****”
搞个cloudbase,搭建云函数开发环境
开搞开搞
先整个 cloudbase 环境
这里因为就简简单单写个函数,创建空模板就好啦。
因为小草的项目是没有登录态的啦。这里就允许匿名访问吧。
我们再加白几个域名吧。
开始创建一个云函数吧。
创建好了后,就可以开始在线上写代码了。
但是,作为一个很厉害的程序员,当然要在本地写代码啦。
继续配置
安装腾讯云开发cli工具
- sudo npm install -g @cloudbase/cli
完了后,找个空文件夹。
- tcb -h
呃,列出命令看看。
登录 -> 同步云函数列表 -> 同步云函数内容啥的。自己看文档吧
开始写云函数啦,写个发邮件的服务。
怎么写?
当然是 要先 搜它一搜啊 !
开始写接口代码
从广大的互联网搬砖工那里学到了,用node写邮件服务,当然是用库啊!
这里用的库名叫 nodemailer
按照示例咱们小心翼翼来一点一点的copy。
- npm install nodemailer
- const nodemailer = require('nodemailer');
-
- const transporter = nodemailer.createTransport({
- service: 'qq',
- auth: {
- user: '你的邮箱地址',//发送者邮箱
- pass: '授权码' //授权码,在准备工作中开启服务时候的授权码
- }
- });
-
- const mailOptions = {
- from: 'xxxxxx@qq.com', // 发送者
- to: 'xxxxxx@qq.com', // 接受者,可以同时发送多个,以逗号隔开
- cc: ',xxxxx@qq.com',//抄送
- subject: '发送邮件测试', // 标题
- text: 'Hello world', // 文本
- html: `<h2>NodeJS发送邮件测试</h2>`
- };
-
- transporter.sendMail(mailOptions, function (err, info) {
- if (err) {
- console.log(err);
- return;
- }
- console.log(`发送成功:${info.accepted}`);
- });
抄完了,我摸了下日渐稀疏的头发。陷入了沉思!
这里还需要搞个发件人邮箱认证呀! 那得去弄下。那就再来一番操作。
先拿我的qq邮箱做个试验吧,进去后,拿到授权码。步骤如下。
再一看,还是不对劲呀!这里 发件人 是不是得需要配置呀。不能写死吧!别人要用怎么办。那得写个配置表呀。
那!
解决方案:开个云数据库,选用特定的配置单。
1、创建一个集合
2、写一条配置数据先
接下来,干点正事,写代码。
- const nodemailer = require("nodemailer");
- const cloudbase = require("@cloudbase/node-sdk");
-
- const cloudApp = cloudbase.init({
- region: "ap-guangzhou",
- // 环境可以不写,默认当前环境
- });
-
- const db = cloudApp.database();
-
- function getEmailInstance(options) {
- const transport = {
- host: options.host,
- secureConnection: true, // 使用SSL方式(安全方式,防止被窃取信息)
- auth: {
- user: options.email,
- pass: options.pass,
- },
- };
- return nodemailer.createTransport(transport);
- }
-
- function sendEmail(opt, sendData) {
- return new Promise((resolve, reject) => {
- const mailTransport = getEmailInstance(opt);
- const options = {
- from: `"${opt.name}" <${opt.email}>`,
- // to: sendData.to,
- // // cc : '' //抄送
- // // bcc : '' //密送
- // subject: "一封来自Node Mailer的邮件",
- // text: "一封来自Node Mailer的邮件",
- // html: '<p>html标签文本</p>',
- ...sendData,
- };
- mailTransport.sendMail(options, function (err, msg) {
- if (err) {
- reject(err);
- } else {
- resolve();
- }
- });
- });
- }
-
-
- const email = {
- send: async (data, context) => {
- // 查库,获取options
- try {
- const dbRes = await db.collection("email-user").doc(data.appid).get();
- const row = dbRes.data[0];
- await sendEmail(row, data);
- return { code: 0, msg: "发送成功" };
- } catch (e) {
- console.error(e);
- return { code: 500, msg: "邮件发送失败", error: e };
- }
- },
- };
好,邮件服务的api主体基本上是搞完了,但是,我发现了一个有趣的地方。
这个nodemailer ,它是可以发html作为邮件文本的呀。邮件参数可参考 nodemailer参数配置
那前端不得搞个富文本编辑器呀,不然,都没意思!
那得搞啊!!!
搞富文本编辑器
怎么搞呢?先问问?
钓友们给我推荐了两个,看了看,选 wangeditor 吧,毕竟文档是中文的。
写代码写代码~~~
因为是nuxt项目中引入外部库,所以先得写个插件来引入
nuxt.config.js
- plugins: [
- { src: '~/plugins/wangEditor', ssr: false },
- ],
plugins/wangEditor.js
- import Wangeditor from 'wangeditor';
- import Vue from 'vue';
-
- Vue.prototype.$wangeditor = (content) => new Wangeditor(content);
WangEditor.vue 组件
- <template>
- <div :id="id"></div>
- </template>
-
- <script>
- export default {
- name: 'WangEditor',
- data() {
- return {
- id: 'e',
- editor: null,
- };
- },
- model: {
- prop: 'val',
- event: 'change',
- },
- props: {
- val: {
- type: String,
- defalut() {
- return '';
- },
- },
- },
- watch: {},
- mounted() {
- this.id = `e${new Date().getTime()}`;
- this.$nextTick(this.initEditor);
- },
- methods: {
- initEditor() {
- const editor = this.$wangeditor(`#${this.id}`);
- this.editor = editor;
- // 配置 onchange 回调函数
- editor.config.onchange = (newHtml) => {
- this.$emit('change', newHtml);
- };
- // 配置触发 onchange 的时间频率,默认为 200ms
- editor.config.onchangeTimeout = 500; // 修改为 500ms
- editor.config.customUploadImg = this.uploadEditorFile;
- editor.config.customUploadVideo = this.uploadEditorFile;
- editor.config.menus = [
- 'head',
- 'bold',
- 'fontSize',
- 'fontName',
- 'italic',
- 'underline',
- 'strikeThrough',
- 'indent',
- 'lineHeight',
- 'foreColor',
- 'backColor',
- 'link',
- 'justify',
- 'quote',
- 'image',
- 'table',
- 'code',
- 'splitLine',
- 'undo',
- 'redo',
- ];
- editor.create();
- editor.txt.html(this.val);
- },
- },
- };
-
- </script>
效果:
然后,似乎,又不对劲啊!!!
富文本图片编辑,那不得搞一个图片对象存储的能力呀。
???
cloudebase 云存储的使用
先搞下配置。
因为小草这个项目是不需要登录的,所以这里暂时先搞成公共读公共写。
这样搞不安全,请不要效仿
然后写两个前端方法,来做文件上传。
- function uploadFile(file) {
- const cloudPath = `${new Date().toLocaleDateString().replace(/\\//gi, '-')}/${new Date().getTime()}.${file.name.split('.').reverse()[0]}`;
- return cloudApp.uploadFile({
- cloudPath,
- filePath: file,
- });
- }
-
- function uploadFiles(arr) {
- return Promise.all(arr.map((v) => uploadFile(v)));
- }
然后,就用 wangeditor 绑定自定义上传文件的方式进行绑定就成了。参考文档
然后,我又发现,咱们现在在前端,已经有两个需要调用 cloudebase 的功能了。那不得?
抽出来!!!抽出来。在nuxt中的话,就搞成一个 插件 吧。
- import cloudbase from '@cloudbase/js-sdk';
- import Vue from 'vue';
-
- const FUNCTION_NAME = 'tools';
- const APPID = '<在云数据库中生成的那个配置单的id>';
-
- const cloudApp = cloudbase.init({
- env: '<环境>',
- region: '<地域>',
- });
-
- function sendEmail(data) {
- return cloudApp.callFunction({
- name: FUNCTION_NAME,
- data: {
- action: 'email.send',
- data: {
- appid: APPID,
- ...data,
- },
- },
- });
- }
-
- function uploadFile(file) {
- return cloudApp.uploadFile({
- cloudPath: `${new Date().toLocaleDateString().replace(/\\\\//gi, '-')}/${new Date().getTime()}.${
- file.name.split('.').reverse()[0]
- }`,
- filePath: file,
- });
- }
-
- function uploadFiles(arr) {
- return Promise.all(arr.map((v) => uploadFile(v)));
- }
-
- Vue.prototype.$cloudtool = {
- uploadFiles,
- uploadFile,
- sendEmail,
- };
这个时候,功能都实现得差不多了。
我又摸了摸的稀疏的头发,既然前端都以抽成了一个独立的插件,我服务端废了那么大的劲就只实现了一个功能,难道就没法扩展吗?
扩展云函数的功能
基本思路就是,调用云函数的时候,其中一个路由参数代表要访问的功能,然后在云函数入口根据不同的路由做分发。
云函数入口 index.js
- 'use strict';
-
- const actions = require("./actions/index.js");
-
- exports.main = async (event, context, callback) => {
- try{
- return actions(event, context)
- } catch (e) {
- return {
- code: 401,
- msg: '参数错误',
- e
- }
- }
- };
主路由 /actions/index.js
- const email = require("./email");
- module.exports = async function(event, context) {
- // 总的路由拦截
- try {
- const {action,data} = event
- const farr = action.split('.')
- return email({
- action: farr[1],
- data,
- }, context)
- } catch(e) {
- throw new Error()
- }
- }
子路由 /actions/email.js
- const email = {
- send: async (data, context) => {
- try {
- const dbRes = await db.collection("email-user").doc(data.appid).get();
- const row = dbRes.data[0];
- await sendEmail(row, data);
- return { code: 0, msg: "发送成功" };
- } catch (e) {
- console.error(e);
- return { code: 500, msg: "邮件发送失败", error: e };
- }
- },
- };
-
- module.exports = async function (event, context) {
- const { action, data } = event;
- return email[action](data, event);
- };
前端调用的话,传参格式如下:
- return cloudApp.callFunction({
- name: 'tools',
- data: {
- action: 'email.send',
- data: {
- appid: APPID,
- ...
- },
- },
- });
那么,从现在开始,我的云函数,就可以扩展出很多路由啦。完美!
交差了
我找到了小草,很自信的给他演示了一遍效果。
小草也摸了摸他稀疏的头发,思考了一下。
“很棒,但是,暴露在外的邮箱发送功能,得有安全问题吧?是不是得想个办法处理一下?”
那么,我怀着沉重的心情,在腾讯云搜了搜。。。
未完待续.................................................