介绍
这篇博客文章重新审视了我(显然)“查看这个新的React框架”系列。
具体来说,我将通过在“ Grunge Stack”上构建的Digital Canvas Development website上使用的联系表格实现。
整个网站,包括此处涵盖的细节,可在GitHub上找到:https://github.com/digital-canvas-dev/digitalcanvas.dev
项目概况
我的应用程序是一个简单的网站,它将充当我新业务的登陆页面。它主要由静态内容和联系表组成。
混音是一个完整的堆栈框架,可共定位服务器端和客户端代码。 Grunge堆栈带有许多功能和库,包括通过Architect(例如npx arc
)部署AWS。许多AWS工具都有资格使用低流量站点的自由层。
本指南假定您已经部署了应用程序或已准备就绪。
要获得端到端的联系表,我将使用AWS简单电子邮件服务(SES)。为了防止垃圾邮件,我将使用Google recaptcha(v2)。
组件设置
初始形式看起来与官方Remix "blog" tutorial的形式非常相似。简而言之,我们从这样的东西开始:
import { Form, useActionData, useSubmit } from '@remix-run/react';
import { InputText } from 'my-component-library';
export const Contact = () => {
const actionData = useActionData();
const submit = useSubmit();
const onSubmit = async (e) => {
await submit(e.currentTarget);
};
return (
<section>
<Form method="POST" onSubmit={onSubmit}>
<InputText
name="name"
label="Name"
errorFeedback={actionData?.errors?.name ?? null}
/>
{/* ... other fields ... */}
<button type="submit">Send</button>
</Form>
</section>
);
};
和我们的action
的核心看起来像这样:
export const action = async ({ request }) => {
const formData = await request.formData();
// we'll pseudocode this away.
// it will return an object of errors, if any.
const errors = validate(formData);
if (errors) {
return errors;
}
// todo: avoid spam
// todo: send the email
return json({
success: true,
successMessage: 'Message sent! Expect to hear back soon.',
});
};
您会注意到两个重要的todo
s,我们将以相反的顺序解决,以便我们可以首先解决更有趣的部分ð
使用AWS简单电子邮件服务(SES)发送电子邮件
要与SES集成,我拉了AWS SDK(v3):
npm i @aws-sdk/client-ses
@aws-sdk/client-ses
是围绕AWS SES V3的包装器,其文档是here。
**重要说明:您的lambda功能必须使用nodejs v18使用AWS V3 SDK。否则,您需要使用V2 SDK。
使用AWS与Grunge堆栈一起使用AWS的一件很酷的事情是,只要您已经遵循deployment steps,client-ses
库就可以使用.deploy script中设置的环境变量的AWS键。 。
现在(查看了许多接口和文档之后)action
实现变得清晰:我们可以简单地创建一个sesclient的实例,然后使用它来发送电子邮件!
架构注:
我们可以将这些方法分解为一些逻辑(可重复使用,可测试的)部分。
以这种方式,我们可以将实例化在一个地方并稍后重复使用。
将所有内容放在一起,逻辑可能看起来像这样...
仅服务器的ses.server.ts
文件:
import { ActionArgs, json } from '@remix-run/node';
import { SendEmailCommand, SendEmailCommandInput, SESClient } from '@aws-sdk/client-ses';
export const sendEmail = async (params: SendEmailCommandInput) => {
const sesClient = new SESClient({
region: 'us-east-1',
});
const command = new SendEmailCommand(params);
return await sesClient.send(command);
};
和更新的操作:
export const action = async ({ request }: ActionArgs): Promise<{ success: true, successMessage: string; } | {
success: false,
errors: { form: string }
}> => {
const formData = await request.formData();
const requesterName = formData.get('name');
const params = {
Source: 'no-reply@...',
Destination: {
ToAddresses: ['...']
},
Message: {
Subject: {
Data: 'Form submission'
},
Body: {
Html: {
Data: `Someone submitted the contact form: Name ${requesterName}, Email: ...`
}
}
}
};
const resp = await sendEmail(params);
const sentError = resp.$metadata.httpStatusCode === 200 ? null : { form: 'Error sending email.' };
if (sentError) {
return json({
success: false,
errors: sentError
});
}
return json({
success: true,
successMessage: 'Thank you for reaching out! Expect to hear back soon.'
});
};
在这一点
验证域并设置完成后,您可以从开发环境发送测试电子邮件!
使用Google Recaptcha V2防止垃圾邮件
但是,此时部署是有风险的。即使您只向自己的电子邮件地址发送电子邮件,恶意演员也可能会尝试不断提交您的表格,以限制您或收取您的AWS账单。
代替(或除)预防站点垃圾邮件的垃圾邮件,我们将确保通过添加复选框验证码提交表格。我决定不使用v3,因为坦率地说,我不喜欢右下角的“被recaptcha”徽章。
首先,您需要创建一个recaptcha here。填写标签,选择Challenge (v2)
和"I'm not a robot" Checkbox
,然后添加码头名称(验证码)将打开(当您在这里时,最好为localhost
创建一个,如果您使用的(如果您使用它)为登台环境创建另一个名称)。
您会出现“站点密钥”和“秘密密钥”,您可以添加到1).env
文件,以及2)GitHub repo设置。
我分别命名为CAPTCHA_SITE_KEY
和CAPTCHA_SECRET
。
网站键将链接表单要recaptcha的站点,并且秘密密钥将仅使用服务器端来验证浏览器中recaptcha生成的值。
接下来,我们将使用npm i react-google-recaptcha
安装koude13 library。
在页面上导入它:
import ReCAPTCHA from 'react-google-recaptcha';
并将其渲染在组件中:
export const Contact = () => {
+ const [recaptchaValue, setRecaptchaValue] = useState<string | null>(null);
+ const recaptchaRef = useRef<ReCAPTCHA>(null);
const actionData = useActionData();
const submit = useSubmit();
const onSubmit = async (e) => {
await submit(e.currentTarget);
+ setRecaptchaValue(null);
+ recaptchaRef?.current?.reset();
};
+ const handleRecaptchaChange = (value: string | null) => {
+ setRecaptchaValue(value);
+ };
return (
<section>
<Form method='POST' onSubmit={onSubmit}>
<InputText
name='name'
label='Name'
errorFeedback={actionData?.errors?.name ?? null}
/>
{/* ... other fields ... */}
+ <input type='hidden' name='recaptchaValue' value={recaptchaValue} />
+ <ReCAPTCHA
+ ref={recaptchaRef}
+ onChange={handleRecaptchaChange}
+ />
<button type='submit'>Send</button>
</Form>
</section>
);
};
为此,组件需要访问CAPTCHA_SITE_KEY
,因此我们可以使用加载程序:
export const loader = async (): Promise<TypedResponse<{ ENV: Pick<Globals, 'CAPTCHA_SITE_KEY'> }>> => {
return json<{
ENV: Pick<Globals, 'CAPTCHA_SITE_KEY'>;
}>({
ENV: {
CAPTCHA_SITE_KEY: `${process.env.CAPTCHA_SITE_KEY}`,
}
});
};
并使用useLoaderData
的组件访问数据:
const data = useLoaderData<{ ENV: Pick<Globals, 'CAPTCHA_SITE_KEY'> }>();
最后(对于组件),我们将填写真正的sitekey
Prop:
<ReCAPTCHA
ref={recaptchaRef}
onChange={handleRecaptchaChange}
+ sitekey={data.ENV.CAPTCHA_SITE_KEY}
/>
我们需要再次更新操作,以验证recaptcha:
export const action = async ({ request }: ActionArgs): Promise<{ success: true, successMessage: string; } | {
success: false,
errors: { form: string }
}> => {
const formData = await request.formData();
+ const recaptchaValue = formData.get('recaptchaValue');
+
+ const captchaResponse = await validateCaptcha(recaptchaValue);
+
+ if (!captchaResponse.success) {
+ return json({
+ success: false,
+ errors: {
+ recaptchaValue: 'Invalid ReCAPTCHA response.',
+ },
+ });
+ }
const requesterName = formData.get('name');
// params, etc...
};
我们可以创建validateCaptcha
函数并将其放入captcha.server.ts
文件中:
const ReCaptchaURL = 'https://www.google.com/recaptcha/api/siteverify';
export const validateCaptcha = async (
recaptchaValue: FormDataEntryValue | null
) => {
const captchaResponse = await fetch(ReCaptchaURL, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: `secret=${process.env.CAPTCHA_SECRET}&response=${recaptchaValue}`,
});
return await captchaResponse.json();
};
CAPTCHA_SECRET
将从您的构建或.env文件中提取,没有什么可做的!
一个完整的实现,带有更多字段和一些样式可能看起来像这样(至少是我的写作,我确实可以!):
谢谢您的阅读!
我在建立解决方案后发现了this article的道具,这给了我一些改进的想法。