0成本开发ChatGPT微信小程序
0成本开发ChatGPT微信小程序

0成本开发ChatGPT微信小程序

Tags
Vue
Next.js
Published
June 20, 2023
Author
 
notion image

背景

3月份时,想着用ChatGPTApi接口来开发一个简单的小程序来玩玩,这也是我第一次开发小程序,为了快速开发,我最后选定了uniapp + uncloud 云开发,原因是使用uniapp我可以使用Vue3+Tailwindcss快速构建页面UI和组织逻辑,使用unicloud是因为我需要数据库存储服务并想快速开发后台接口。实际使用下来,unicloud也确实省去了很大的环境准备或者部署工作,于是我的第一个小程序就这样上线了,但是来了,Unicloud也有很多缺点
收费
最便宜一个月20,关键这其中云函数资源使用量 = 函数配置内存 X 运行计费时长,10万GBs很可能不够。
notion image
开发工具和代码写法强要求
unicloud和Hbuilder编辑器是强绑定的,你必须使用这个编辑器,并且比如数据库操作也有一套它指定的写法,那么这就会有一定的学习成本
node版本太低
使用中发现,unicloud云函数的node版本最大好像是14,具体版本我忘了,目前node稳定版都18了,并且很多npm包都要求更高版本了,这就使得很多包无法直接使用
最近我在学习Next.js并用其开发了一个导航网站时,体验下来我突然发现,这不就是免费的云服务么,于是我放弃了Unicloud转而使用Next.js重构了我的小程序,便有了这篇文章,以个人小程序举例,结合Nextjs来实现0成本开发小程序。
总的来看,如果你不太熟悉nodejs开发,愿意付费,但是想快速开发后端服务时,它提供了比如微信支付、图形验证码、文本内容安全识别等很多插件,只要简单熟悉他指定的一些语法并可上手开发了,这种情况下推荐unicloud,但如果你熟悉nodejs开发,一些简单的业务接口和数据库处理就能满足要求,那么免费的更符合我们熟知的云函数写法可能选择更好。
💡本项目已上线,你可以微信小程序搜索:智障问答Bot 来体验,同时本项目也已开源,你可以在项目开源地址查看完整代码

实践

在我的使用Nextjs快速开发全栈导航网站文章中,我们从数据中获取网站数据并展示,例如你通过接口 https://webnav.codefe.top/api/links 便可以获取页面所有数据。接下来以一个ChatGPT小程序来实践,功能包括用户登录注册、ChatGPT接口调用生成文案两个功能。
小程序基础目录结构如下
├── index.html ├── package.json ├── project.config.json ├── README.md ├── src │ ├── apis │ ├── App.vue │ ├── components │ ├── composables │ ├── layouts │ ├── main.ts │ ├── manifest.json │ ├── pages │ ├── pages.json │ ├── static │ ├── stores │ ├── theme.json │ └── uni.scss ├── tsconfig.json ├── unocss.config.ts └── vite.config.ts
页面UI部分没啥特别的,使用了uniapp + Vite + UnoCSS + Pinia + uview-plus ,用这套下来开发体验还可以,就是有部分Unocss的写法还没支持全,偶尔会遇到写了但是没生效的问题,页面UI如下。
notion image

用户注册

使用下面命令初始化后台server部分
npx create-next-app@latest server
创建用户表,我这使用的是MongoDB官网提供的免费版本,使用Prisma ORM工具操作数据库,关于其使用可以参考我上一篇文章。执行
npx prisma init
会创建prisma/schema.prisma文件,创建模型如下
generator client { provider = "prisma-client-js" } enum Role { ADMIN USER } datasource db { provider = "mongodb" url = env("DATABASE_URL") } model User { id String @id @default(auto()) @map("_id") @db.ObjectId nickname String? wx_openid String @unique credit Int? role Role @default(USER) avatar String? createdAt DateTime @default(now()) @map(name: "created_at") updatedAt DateTime @updatedAt @@map(name: "user") }
其中
  • provider指明我们是使用mongdb
  • wx_openid是用户微信小程序的唯一openid
  • credit标识用户对话可使用次数
  • role标识用户角色
接下来创建注册接口,注册流程图如下
notion image
新增app/api/auth/route.ts文件,代码如下
import { NextRequest } from "next/server"; import prisma from '@/lib/db' import type { User } from "@prisma/client"; import { signJWT } from "@/lib/utils"; const { WX_APPID, WX_SECRET, DEFAULT_CREDIT } = process.env; /** * 注册用户 * @param req * @returns */ export async function POST(req: NextRequest) { const body = await req.json(); const { code } = body; const response = await fetch(`https://api.weixin.qq.com/sns/jscode2session?appid=${WX_APPID}&secret=${WX_SECRET}&js_code=${code}&grant_type=authorization_code`) const jscode2session = await response.json(); let user: Partial<User | null> = await prisma.user.findUnique({ where: { wx_openid: jscode2session.openid, }, }) if (!user) { user = await prisma.user.create({ data: { wx_openid: jscode2session.openid, credit: Number(DEFAULT_CREDIT), }, }) } const token = await signJWT({ sub: user.id! }, { exp: '7d' }); delete user.wx_openid; return new Response(JSON.stringify({ status: "success", data: { token, user, }, })) }
该文件创建了一个对应/api/authPOST请求路径接口,从参数中获取小程序调用uni.login() 获取到的code,通过小程序appidapp_secret调用微信接口获取用户的小程序openid, 然后通过prisma.user.create创建保存用户到数据库,生成jwt并后续用其来处理接口认证。

鉴权中间件

根目录下创建middleware.ts文件,代码如下
import { NextRequest, NextResponse } from "next/server"; import { getErrorResponse, verifyJWT } from "./lib/utils"; export async function middleware(req: NextRequest) { let token: string | undefined; if (req.headers.get("Authorization")?.startsWith("Bearer ")) { token = req.headers.get("Authorization")?.substring(7); } if (!token) { return getErrorResponse( 401, "You are not logged in. Please provide a token to gain access." ); } const response = NextResponse.next(); try { if (token) { const { sub } = await verifyJWT<{ sub: string }>(token); (req as AuthenticatedRequest).user = { id: sub }; response.headers.set("X-USER-ID", sub); } } catch (error) { return getErrorResponse(401, "Token is invalid or user doesn't exists"); } return response; } export const config = { matcher: ["/api/user/:path*", "/api/chat-stream"], };
header头中渠道jwt并解析出userId, 并注入到X-USER-ID ,后续请求从这个字段取,其中matcher配置了需要鉴权的api路径

对话接口

接下来创建app/api/chat-stream/route.ts文件,代码如下
import type { NextRequest } from "next/server"; import prisma from '@/lib/db' import { ChatGPTMessage, OpenAIStream, OpenAIStreamPayload } from "@/lib/openAIStream"; import { User } from "@prisma/client"; const handler = async (req: NextRequest) => { const body = await req.json(); const { messages } = body; const userId = req.headers.get("X-USER-ID"); const user: User | null = await prisma.user.findUnique({ where: { id: userId!, }, }); const isAdmin = user.role === 'ADMIN'; if (!isAdmin && (!user.credit || user.credit <= 0)) { return new Response(JSON.stringify({ status: "fail", message: '使用次数不足', })) } if (!messages) { return new Response(JSON.stringify({ status: "fail", message: '请先输入你的问题', })) } const payload: OpenAIStreamPayload = { model: "gpt-3.5-turbo", messages, temperature: 0.7, top_p: 1, max_tokens: 800, stream: true, }; const stream = await OpenAIStream(payload); !isAdmin && await prisma.user.update({ where: { id: userId!, }, data: { credit: { decrement: 1 }, }, }) return new Response(stream); }; export { handler as POST, handler as GET };
首先根据X-USER-ID查询用户,如果用户使用次数不足终止请求,接下来调用openai对话API接口处理,结束后通过decrement将用户可使用次数减1。

部署

vercel部署时选择server文件夹,构建命令为
pnpx prisma generate && next build
填入环境变量信息后点击部署即可
notion image

总结

本文以自己的小程序举例,前端页面通过微信小程序平台托管,后台服务使用Next.js开发,通过vercel免费托管,结合免费mongodb实现了0成本开发和上线微信小程序,本项目已上线,你可以微信小程序搜索:智障问答Bot 来体验,同时本项目已开源,你可以在项目开源地址查看完整代码,希望这篇文章对大家有所帮助,欢迎体验和star,感谢。