likes
comments
collection
share

0成本开发ChatGPT微信小程序

作者站长头像
站长
· 阅读数 11

背景

3月份时,想着用ChatGPTApi接口来开发一个简单的小程序来玩玩,这也是我第一次开发小程序,为了快速开发,我最后选定了uniapp + uncloud 云开发,原因是使用uniapp我可以使用Vue3+Tailwindcss快速构建页面UI和组织逻辑,使用unicloud是因为我需要数据库存储服务并想快速开发后台接口。实际使用下来,unicloud也确实省去了很大的环境准备或者部署工作,于是我的第一个小程序就这样上线了,但是来了,Unicloud也有很多缺点

  • 收费

    最便宜一个月20,关键这其中云函数资源使用量 = 函数配置内存 X 运行计费时长,10万GBs很可能不够。

    0成本开发ChatGPT微信小程序

  • 开发工具和代码写法强要求

    unicloud和Hbuilder编辑器是强绑定的,你必须使用这个编辑器,并且比如数据库操作也有一套它指定的写法,那么这就会有一定的学习成本

  • node版本太低

    使用中发现,unicloud云函数的node版本最大好像是14,具体版本我忘了,目前node稳定版都18了,并且很多npm包都要求更高版本了,这就使得很多包无法直接使用

最近我在学习Next.js并用其开发了一个导航网站时,体验下来我突然发现,这不就是免费的云服务么,于是我放弃了Unicloud转而使用Next.js重构了我的小程序,便有了这篇文章,以个人小程序举例,结合Nextjs来实现0成本开发小程序。

总的来看,如果你不太熟悉nodejs开发,愿意付费,但是想快速开发后端服务时,它提供了比如微信支付、图形验证码、文本内容安全识别等很多插件,只要简单熟悉他指定的一些语法并可上手开发了,这种情况下推荐unicloud,但如果你熟悉nodejs开发,一些简单的业务接口和数据库处理就能满足要求,那么免费的更符合我们熟知的云函数写法可能选择更好。

💡本项目已上线,你可以微信小程序搜索:智障问答Bot 来体验,同时本项目也已开源,你可以在项目开源地址查看完整代码

实践

在我的使用Nextjs快速开发全栈导航网站文章中,我们从数据中获取网站数据并展示,例如你通过接口 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如下。

0成本开发ChatGPT微信小程序

用户注册

使用下面命令初始化后台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标识用户角色

接下来创建注册接口,注册流程图如下

0成本开发ChatGPT微信小程序

新增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

填入环境变量信息后点击部署即可

0成本开发ChatGPT微信小程序

总结

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