一个研发是如何一步一步把一个小需求越搞越大的

通过一个小小的需求来玩玩腾讯云的云开发

前言

我有一个同事。

他叫小草。

是个开发,男的。

需求背景

小草呢,最近在搞一个开源小项目。

这两天找到我,”小丁啊,给我加个小功能。简单!“

需求:页面上加几个输入框,一个输入框填一个邮箱地址,用户提交后,给这个邮箱发个邮件。

开始整活

”小草啊,这个需求简单,给我一个接口,我一个切图仔唰唰唰切一个页面,完了调它一个接口,齐活。“

切图仔,切页面

小草的这个项目啊,是一个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,
  • ...
  • },
  • },
  • });

那么,从现在开始,我的云函数,就可以扩展出很多路由啦。完美!

交差了

我找到了小草,很自信的给他演示了一遍效果。

小草也摸了摸他稀疏的头发,思考了一下。

“很棒,但是,暴露在外的邮箱发送功能,得有安全问题吧?是不是得想个办法处理一下?”

那么,我怀着沉重的心情,在腾讯云搜了搜。。。


未完待续.................................................

本站文章资源均来源自网络,除非特别声明,否则均不代表站方观点,并仅供查阅,不作为任何参考依据!
如有侵权请及时跟我们联系,本站将及时删除!
如遇版权问题,请查看 本站版权声明
THE END
分享
二维码
海报
<<上一篇
下一篇>>