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

2021-08-28 12:22:43 浏览数 (3)

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

前言

我有一个同事。

他叫小草。

是个开发,男的。

需求背景

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

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

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

开始整活

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

切图仔,切页面

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

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

交差了

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

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

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

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


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

0 人点赞