前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >一个研发是如何一步一步把一个小需求越搞越大的

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

原创
作者头像
vannding
发布2021-08-28 12:22:43
1.8K6
发布2021-08-28 12:22:43
举报
文章被收录于专栏:vann的专栏

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

前言

我有一个同事。

他叫小草。

是个开发,男的。

需求背景

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

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

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

开始整活

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

切图仔,切页面

小草的这个项目啊,是一个nuxt的项目,UI框架用的ant-design。nuxt嘛,大家知道,跟vue差不多的写法,这简单啊,有手就行。开搞开搞。

首先,写个界面出来。

代码语言:txt
复制
<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 吧。

代码语言:txt
复制
/***********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工具

代码语言:txt
复制
sudo npm install -g @cloudbase/cli

完了后,找个空文件夹。

代码语言:txt
复制
tcb -h

呃,列出命令看看。

登录 -> 同步云函数列表 -> 同步云函数内容啥的。自己看文档吧

开始写云函数啦,写个发邮件的服务。

怎么写?

当然是 要先 搜它一搜啊 !

开始写接口代码

从广大的互联网搬砖工那里学到了,用node写邮件服务,当然是用库啊!

这里用的库名叫 nodemailer

按照示例咱们小心翼翼来一点一点的copy。

代码语言:txt
复制
     npm install nodemailer
代码语言:txt
复制
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、写一条配置数据先

接下来,干点正事,写代码。

代码语言:txt
复制
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

代码语言:txt
复制
plugins: [
    { src: '~/plugins/wangEditor', ssr: false },
],

plugins/wangEditor.js

代码语言:txt
复制
import  Wangeditor  from  'wangeditor';
import  Vue  from  'vue';

Vue.prototype.$wangeditor  = (content) =>  new  Wangeditor(content);

WangEditor.vue 组件

代码语言:txt
复制
<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 云存储的使用

先搞下配置。

因为小草这个项目是不需要登录的,所以这里暂时先搞成公共读公共写。

这样搞不安全,请不要效仿

然后写两个前端方法,来做文件上传。

代码语言:txt
复制
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中的话,就搞成一个 插件 吧。

代码语言:txt
复制
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

代码语言:txt
复制
'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

代码语言:txt
复制
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

代码语言:txt
复制
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);
};

前端调用的话,传参格式如下:

代码语言:txt
复制
return  cloudApp.callFunction({
    name: 'tools',
    data: {
        action: 'email.send',
        data: {
            appid: APPID,
            ...
        },
    },
});

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

交差了

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

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

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

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


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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 需求背景
  • 开始整活
    • 切图仔,切页面
      • 搞个cloudbase,搭建云函数开发环境
        • 开始写接口代码
          • 搞富文本编辑器
            • cloudebase 云存储的使用
              • 扩展云函数的功能
              • 交差了
              相关产品与服务
              云函数
              云函数(Serverless Cloud Function,SCF)是腾讯云为企业和开发者们提供的无服务器执行环境,帮助您在无需购买和管理服务器的情况下运行代码。您只需使用平台支持的语言编写核心代码并设置代码运行的条件,即可在腾讯云基础设施上弹性、安全地运行代码。云函数是实时文件处理和数据处理等场景下理想的计算平台。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档