基于邮件服务实现了最经济的注册方案

803 阅读5分钟

背景

最近开源了一款面向中后台的低代码系统,热度持续高涨,微信群已近百人,PV已突破1w次。很多用户反馈,为啥没有注册功能,我想自己注册一个账号去体验,不想共用demo账号。

于是本周就开始思考如何实现一个注册功能。注册是用 微信扫码登录手机号登录还是邮箱登录

本文主要把我的思考路径和最终方案整理给大家,仅供参考。同时这两周也持续迭代了marsview系统,提供了很多有趣的功能,欢迎大家文章末尾体验。

效果展示

image.png

方案选择

微信扫码

微信扫码登录自然是体验最好的方式,然后我作为一个个人开发者,不具备一些条件。因为微信扫码需要企业主体,同时需要开通微信认证,这些都比较耗时以及有一定的门槛。

我想后续我会逐步考虑,但当下不是最好的选择。

手机号登录

手机号登录虽然可以保存高质量的用户群体,但是也会存在一些问题,比如:用户注册意愿?短信如何防刷?购买短信成本等。

一般很多用户面对一个新网站,用手机号注册意愿不高,因为担心手机号泄露和骚扰,所以我自己主观感觉,手机号注册意愿很低。

其次短信防刷,目前行业倒是有一些方案可供参考,比如:

  1. 调用短信接口前,需要先做滑块验证,通过以后才能调用短信接口。

image.png

插件:rc-slider-captcha 或者 react-slider-vertify

图片参考:picsum.photos/ 这个网站可以得到很多用作验证的背景图片。

  1. 接口做IP次数验证,每个IP每天限制5次。
  2. 接口做origin限制。
  3. 前后端增加签名验证。使用对称加密对参数、时间戳、随机数等加密,生产签名,传给后端做验证,通过后,方可调用。

这些方案,我觉得都可行,但目前我的低代码系统主打开源,暂时还不想增加额外的购买成本,所以手机号的方案,暂时不做,等后续平台稳定以后,再考虑接入手机号。

邮箱注册

邮箱注册应该算是当前最经济实惠的一个方案了,几乎没有成本,而且用户使用邮箱注册的意愿要比手机号更强烈,因为手机号担心泄露和骚扰问题,邮箱相对而言好很多。

邮箱注册实现

前端实现

image.png

1. 静态代码如下:
<div className={style.form}>
  <div className={style.title}>账号注册</div>
  <Form
    name="basic"
    layout="vertical"
    className={style.form}
    onFinish={onFinish}
    size="large"
    form={form}
  >
    <Form.Item<FieldType>
      name="userName"
      rules={[
        { required: true, message: '请输入邮箱' },
        { pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/, message: '请输入正确的邮箱' },
      ]}
    >
      <Input prefix={<UserOutlined />} placeholder="请输入个人邮箱" />
    </Form.Item>

    <Form.Item>
      <Space>
        <Form.Item<FieldType> name="code" noStyle rules={[{ required: true, message: '请输入验证码' }]}>
          <InputNumber prefix={<SafetyOutlined />} style={{ width: '100%' }} placeholder="验证码" />
        </Form.Item>
        <Button type="primary" onClick={handleCreateCode} disabled={count > 0} loading={loading1}>
          {count > 0 ? count + 's' : '获取验证码'}
        </Button>
      </Space>
    </Form.Item>

    <Form.Item<FieldType> style={{ marginTop: 32 }} name="userPwd" rules={[{ required: true, message: '请输入密码' }]}>
      <Input.Password prefix={<LockOutlined />} />
    </Form.Item>

    <Form.Item style={{ marginTop: 40 }}>
      <Button type="primary" block htmlType="submit" loading={loading2}>
        登录
      </Button>
    </Form.Item>
    <Form.Item style={{ marginTop: 40 }}>
      <div style={{ textAlign: 'center' }}>
        '已有账号?去登录'
      </div>
    </Form.Item>
  </Form>
</div>
2. 实现一个倒计时
export default function Login() {
    const [count, setCount] = useState(0);
    // 生成验证码
    const handleCreateCode = () => {
        // 调用验证码接口
        ....
        setCount(60);
        ...
    }
    useEffect(() => {
        const timer = setTimeout(() => {
          if (count > 0) {
            setCount(count - 1);
          }
        }, 1000);

        // 清理函数
        return () => clearTimeout(timer);
    }, [count]);
    
    return <>...</>
}

点击获取验证码按钮时,调用接口,初始化count值,通过setTimeout触发倒计时。

3. 按钮在倒计时期间,增加状态判断。
<Button 
    type="primary" 
    onClick={handleCreateCode} 
    disabled={count > 0} 
    loading={loading1}
>
  {count > 0 ? count + 's' : '获取验证码'}
</Button>
  • 增加Loading状态。
  • 如果倒计时结束,需要启用按钮,否则禁用。
  • 按钮显示倒计时的数字。

接口实现

1. 安装nodemailer插件
yarn add nodemailer
2. 使用Koa实现邮箱服务完整配置代码
const nodemailer = require("nodemailer");
const { Keyv } = require("keyv");
const keyv = new Keyv();
/**
 * 用户注册
 */
router.post("/sendEmail", async (ctx) => {
  try {
    const { email } = ctx.request.body;
    if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
      util.fail(ctx, "邮箱不能为空或格式错误");
      return;
    }
    const val = await keyv.get(email);
    if (val) {
      util.fail(ctx, "验证码已发送,请查收");
      return;
    }
    let transporter = nodemailer.createTransport({
      host: "smtp.163.com",
      port: 465,
      auth: {
        user: "xxx@163.com", // 你的163邮箱地址
        pass: "xxx", // 你的邮箱密码
      },
    });

    const random = Math.random().toString().slice(2, 7);

    let mailOptions = {
      from: '"Marsview" <xxx@163.com>', // 发送者地址
      to: email, // 接收者列表
      subject: "Marsview账号注册", // 主题行
      text: "验证码发送", // 纯文字正文
      html: `您当前的验证码为:<b>${random}</b>,3分钟内有效。感谢您成为Marsview一员。`, // HTML正文
    };

    await transporter.sendMail(mailOptions);
    await keyv.set(email, random, 3 * 60 * 1000);
    util.success(ctx, "发送成功");
  } catch (error) {
    util.fail(ctx, "发送失败,请重试");
  }
});

nodemailer插件支持很多格式的邮箱,比如:qq163gmail等,通过这个插件几乎没什么难度就快速实现邮件发送功能。

3. 有效期控制。

这里面有几个问题需要跟大家分享,邮箱发送的验证码有效期怎么控制?大家可能会说redis,的确如此,但是作为个人开发者,难不成再去购买一台redis服务器,那不可能,所以,我们只能选择保存在内存中,占用服务器资源。

  • 安装插件
yarn add keyv

keyv插件是一款键值对保存的插件,它可以对接redismysqlmongo等等,每天下载量3000多万,非常受欢迎,同时它默认是保存在内存中,对于有咱们这种穷鬼来说,再适合不过了。

  • 发送前验证
const { Keyv } = require("keyv");
const keyv = new Keyv();

...

const val = await keyv.get(email);
if (val) {
  util.fail(ctx, "验证码已发送,请查收");
  return;
}

发送之前,先通过keyv.get判断邮箱是否存在,如果存在,说明验证码还没过期,就不要重复发送了。

  • 保存验证码
// 随机生成一个5位验证码
const random = Math.random().toString().slice(2, 7);
// 调用发送API
await transporter.sendMail(mailOptions);
// 保存验证码到缓存中,有效期3分钟
await keyv.set(email, random, 3 * 60 * 1000);

注意:keyv.set 是一个 Promise函数

总结

我就是借用了nodemailerkeyv插件实现了免费的邮件发送和有效期控制的功能,当然在不同阶段,可能会选择不同的方案,或许当下免费的是最适合我的。

欢迎大家体验低代码系统,加入微信群一起沟通交流。这两周,我们又迭代了很多有趣的功能,比如:

  • 优化首页加载
  • 增加个人是市场页面区分。
  • 自定义组件引入AI助手,自动生成代码。
  • 开发抽屉组件、结果组件等。
  • 增加使用文档,录制了一些教学视频(事件流如何使用、综合案例等)。

欢迎在线体验:www.marsview.cc/

开源地址:github.com/JackySoft/m…

文档地址:docs.marsview.cc/

低代码效果图:

image.png

image.png

image.png

02f630d4-45a9-49ab-b358-c22ba3ce5294.gif