后端示例项目:商城会员邀请API

LingYun 9ae5941627 feat: new apidoc in npm run docs 3 月之前
db 1616883398 feat: init node-invite-system 3 月之前
.gitignore 9ae5941627 feat: new apidoc in npm run docs 3 月之前
README.md 9ae5941627 feat: new apidoc in npm run docs 3 月之前
apidoc.json 9ae5941627 feat: new apidoc in npm run docs 3 月之前
index.js 1616883398 feat: init node-invite-system 3 月之前
package-lock.json 9ae5941627 feat: new apidoc in npm run docs 3 月之前
package.json 9ae5941627 feat: new apidoc in npm run docs 3 月之前
test.js 1616883398 feat: init node-invite-system 3 月之前

README.md

示例:会员邀请系统后端API设计

任务简介

  • 实验目标
    • 演示AGI开发系统后端接口全过程
    • 理解将需求拆解成开发任务的方法
    • 强调AGI在测试环节模拟数据和测试脚本的重要性
  • 实验任务
    • 根据需求完成会员邀请系统API设计、数据表设计、接口单元测试。
  • 示例项目

需求描述

商城会员邀请系统是一个用于促进会员之间邀请推广和互动的功能模块,其需求包括但不限于以下几点:

  1. 邀请链接生成:会员可以通过系统生成独一无二的邀请链接,用于邀请他人注册成为新会员。
  2. 邀请追踪:系统需要记录和追踪每个邀请链接的点击量、注册量等数据,以便评估邀请效果。

通过以上需求,商城会员邀请系统可以有效增加会员互动和促进销售,提升用户粘性和活跃度,同时也为商城带来更多的用户流量和销售机会。

业务流程(前后端拆解)

  • 前端
    • 邀请注册页面 https://app/invite/
      • 某某某,邀请您注册成为[XX商城的]会员
      • 通过邀请注册后,可享受会员权益
      • 点击注册 => 手机验证 => 注册成功
    • 邀请统计页面 https://app/invite/analysis/
      • 邀请统计总览
        • 共XXX次点击
        • 共XXX人注册
      • 邀请用户列表
        • 用户名称 手机号 关系距离
          • 小王 131xxxxxxxx 直接邀请
          • 小王 186xxxxxxxx 间接邀请
    • 后端

      • 会员账号注册接口
        • 前端:携带邀请人ID,提交注册手机号+验证码
        • 后端:完成用户注册,并设置inviteID,同时计算上级邀请列表
      • 建立邀请关系接口
        • 当被邀请人未消费,或者无inviteId时,设置邀请人未邀请用户
      • 邀请数据统计接口
        • 查询用户记录表中,链接点击次数,注册总人数
      • 邀请用户列表接口

        • 查询用户记录表中,所有与当前用户相关的用户列表

          
          @startuml
          
          package "前端" {
          [邀请注册页面] --> (点击注册)
          [邀请注册页面] --> (手机验证)
          [邀请注册页面] --> (注册成功)
              
          [邀请统计页面] --> (邀请统计总览)
          [邀请统计页面] --> (邀请用户列表)
          }
          
          package "后端" {
          [会员账号注册接口] --> (前端)
          [建立邀请关系接口] --> (后端)
          [邀请数据统计接口] --> (后端)
          [邀请用户列表接口] --> (后端)
          }
          
          @enduml
          
          @startuml
          
          participant 前端
          participant 后端
          
          == 邀请注册页 ==
          activate 前端
          前端 -> 后端: 会员账号注册接口\n(携带邀请人ID,提交注册手机号+验证码)
          activate 后端
          后端 --> 前端: 完成用户注册,并设置inviteID,\n同时计算上级邀请列表
          deactivate 后端
          前端 -> 后端: 建立邀请关系接口\n(当被邀请人未消费,或者无inviteId时,设置邀请人未邀请用户)
          == 邀请统计页 ==
          
          activate 后端
          后端 --> 前端: 邀请数据统计接口\n(查询用户记录表中,链接点击次数,注册总人数)
          后端 --> 前端: 邀请用户列表接口\n(查询用户记录表中,所有与当前用户相关的用户列表)
          deactivate 后端
          deactivate 前端
          
          @enduml
          

    接口设计

    提示词:请您根据上述需求,来描述所需的四个接口,包括请求方法、请求参数、返回结果。

    会员邀请系统后端API的需求描述可以包括以下内容:

    • 会员账号注册接口
      • 请求方法:POST
      • 请求参数
        • 手机号
        • 验证码
        • 邀请人ID
      • 返回结果
        • {code:200,msg:"成功注册用户"}
        • {code:500,msg:"注册失败,用户已存在"}
    • 建立邀请关系接口
      • 请求方法:GET
      • 请求参数:
        • 邀请人ID
        • 被邀请人ID
        • 返回结果
        • {code:200,msg:"成功建立邀请关系"}
        • {code:500,msg:"无法建立邀请关系"}
    • 邀请数据统计接口
      • 请求方法:GET
      • 请求参数:
        • 当前用户ID
      • 返回结果
        • {code:200,data:{click:12,register:10}}
    • 邀请用户列表接口
      • 请求方法:GET
      • 请求参数:
        • 当前用户ID
      • 返回结果
        • {code:200,data:[{name:"用户名称",mobile:"用户手机",distance:1/2/3/4}]}
          • distance 邀请距离
            • 1 直接邀请
            • 2 间接邀请
            • 3 再间接邀请

    范式设计

    提示词:我的会员邀请系统接口设计如上述,请你帮我分析所需的Parse Schema范式表及具体的字段名称,表名大驼峰,字段小驼峰。可以用plantuml的类图来表示,各个表需要符合设计范式。

    @startuml
    
    package "会员邀请系统" {
        class Member {
            + objectId : String
            + name : String
            + mobile : String
            + registerDate : Date
            + invite:  Pointer<Member>
            + invitePath: Array<Pointer<Member>>
            + inviteDate : date
        }
    
        class InviteEvent {
            + objectId : String
            + member : Pointer<Member>
            + click_count : String
            + register_count : String
        }
    
        Member "1" -- "0..1" InviteEvent : has
    }
    
    @enduml
    

    在上述设计中,我们定义了以下数据表及字段:

    1. Member(会员表)

      • objectId (主键):String 会员ID
      • name:String 姓名
      • mobile:String 手机号
      • registerDate:Date 注册日期
      • invite:Pointer<Member> 邀请人会员指针
      • invitePath:Array<Member.objectId> 邀请人ID递归数组
      • inviteDate:Date 邀请日期
    2. InviteEvent(邀请事件统计表)

      • objectId (主键):String 统计ID
      • member:Pointer<Member> 用户ID
      • type:String 事件类型
        • click: 点击
        • share: 分享
      • count:Number 有效次数 默认1

    这样的设计能够很好地支持会员邀请系统的需求,并符合数据库设计范式。

    架构选型

    • 编程语言:JavaScript
    • 运行环境:node
    • Web框架:Express
    • 接口注释:apidoc
    • 数据库:PostgreSQL

    后端实现

    初始化项目

    • 创建项目目录
      • 在磁盘的workspace目录,创建一个项目名称为
      • server-invite 邀请系统服务端项目
    • 初始化node环境

      • VSCode打开该项目
      • Ctrl+~ 打开终端
      • 输入:npm init 初始化node项目

        npm init
        
    • 创建index.js

    • 安装所需第三方库

      npm i -S express pg-promise
      

      数据库搭建

      数据库链接测试

    • 完成数据库搭建,获取dbname

      • 已创建好的测试数据库:

        • postgresql://web3:666@web2023.fmode.cn:25432/dev

          psql --dbname=postgresql://web3:666@web2023.fmode.cn:25432/dev
          
    • 创建目录:db/

      • 创建SQL测试脚本 db/sql-test.js

        提示词:请帮我用pg-promise生成一个执行SELECT语句并返回结果的示例程序。

        const pgp = require('pg-promise')();
        
        // 连接到数据库
        const db = pgp({
        user: 'web3',
        password: '666',
        host: 'web2023.fmode.cn',
        port: 25432,
        database: 'dev'
        });
        
        // 执行SELECT语句并返回结果的函数
        async function main() {
        try {
        // 查询:数据库版本信息
        const data = await db.any('SELECT version();');
        console.log(data)
        return data;
        } catch (error) {
        console.error('Error executing query:', error);
        return null;
        }
        }
        
        // 调用函数并处理结果
        main()
        

    创建数据库表

    Parse JS PostgreSQL
    字符串 String text
    指针 Pointer text
    数字 Number double precision
    布尔 Boolean boolean
    日期 Date timestamp with time zone
    数组 Array jsonb
    对象 Object jsonb
    经纬度 GeoPoint jsonb

    生成SQL创建语句

    提示词:请帮我根据以上范式设计,生成几张数据库表的CREATE创建语句,字段类型String和Pointer对应的是text,Date对应timestamp with time zone,Array对应jsonb,Number对应double precision。其中大小写敏感,表名字段名用""引起来。

    当然,我可以为您生成这些表的CREATE语句。以下是您请求的数据库表的CREATE语句:

    1. 会员表("Member")

      CREATE TABLE "Member" (
      "objectId" text PRIMARY KEY,
      "name" text,
      "mobile" text,
      "registerDate" timestamp with time zone,
      "invite" text REFERENCES "Member" ("objectId"),
      "invitePath" jsonb,
      "inviteDate" timestamp with time zone
      );
      
    2. 邀请事件统计表("InviteEvent")

      CREATE TABLE "InviteEvent" (
      "objectId" text PRIMARY KEY,
      "member" text REFERENCES "Member" ("objectId"),
      "type" text,
      "click" text,
      "share" text,
      "count" double precision DEFAULT 1
      );
      

    希望这些CREATE语句能够满足您的需求。如果您有任何其他问题或需要进一步帮助,请随时告诉我。

    生成测试数据插入语句

    提示词:请帮我根据以上表结构,生成大量的中文姓名的会员信息,并有多级的邀请关系,同时生成大量的邀请事件,并用INSERT语句表示。

    为了生成大量的中文姓名的会员信息和邀请事件数据,以下是一些示例INSERT语句,可以帮助您填充数据库表:

    1. 插入会员信息:

      INSERT INTO "Member" ("objectId", "name", "mobile", "registerDate", "invite", "invitePath", "inviteDate") 
      VALUES 
      ('1', '张三', '13812345678', '2024-06-16 12:00:00', NULL, '[]', NULL),
      ('2', '李四', '13987654321', '2024-06-16 12:30:00', '1', '["1"]', '2024-06-16 13:00:00'),
      ('3', '王五', '13611112222', '2024-06-16 13:00:00', '2', '["1", "2"]', '2024-06-16 13:30:00'),
      ('4', '赵六', '13533334444', '2024-06-16 14:00:00', '2', '["1", "2"]', '2024-06-16 14:30:00'),
      ('5', '刘七', '13755556666', '2024-06-16 15:00:00', '4', '["1", "2", "4"]', '2024-06-16 15:30:00');
      
    2. 插入邀请事件信息:

      INSERT INTO "InviteEvent" ("objectId", "member", "type", "click", "share", "count") 
      VALUES 
      ('101', '1', '邀请注册', '10', '5', 1),
      ('102', '2', '邀请注册', '8', '3', 1),
      ('103', '3', '邀请注册', '6', '2', 1),
      ('104', '4', '邀请注册', '5', '1', 1),
      ('105', '5', '邀请注册', '4', '1', 1);
      

    您可以根据这个示例,结合随机生成的中文姓名和具体数据,来生成更多的会员信息和邀请事件数据。

    编写SQL迁移脚本

    • 根据上述的:db/sql-test.js脚本

      • 创建新脚本:db/sql-create.js
      • 将原有SQL语句,替换成创建语句和插入语句

        // db/sql-text.js
        const pgp = require('pg-promise')();
        
        // 连接到数据库
        const db = pgp({
        user: 'web3',
        password: '666',
        host: 'web2023.fmode.cn',
        port: 25432,
        database: 'dev'
        });
        
        // 执行SELECT语句并返回结果的函数
        async function main() {
        try {
        // 查询:数据库版本信息
        let sql = `
        CREATE TABLE "Member" (
            "objectId" text PRIMARY KEY,
            "name" text,
            "mobile" text,
            "registerDate" timestamp with time zone,
            "invite" text REFERENCES "Member" ("objectId"),
            "invitePath" jsonb,
            "inviteDate" timestamp with time zone
        );
        CREATE TABLE "InviteEvent" (
            "objectId" text PRIMARY KEY,
            "member" text REFERENCES "Member" ("objectId"),
            "type" text,
            "click" text,
            "share" text,
            "count" double precision DEFAULT 1
        );
        
        INSERT INTO "Member" ("objectId", "name", "mobile", "registerDate", "invite", "invitePath", "inviteDate") 
        VALUES 
        ('1', '张三', '13812345678', '2024-06-16 12:00:00', NULL, '[]', NULL),
        ('2', '李四', '13987654321', '2024-06-16 12:30:00', '1', '["1"]', '2024-06-16 13:00:00'),
        ('3', '王五', '13611112222', '2024-06-16 13:00:00', '2', '["1", "2"]', '2024-06-16 13:30:00'),
        ('4', '赵六', '13533334444', '2024-06-16 14:00:00', '2', '["1", "2"]', '2024-06-16 14:30:00'),
        ('5', '刘七', '13755556666', '2024-06-16 15:00:00', '4', '["1", "2", "4"]', '2024-06-16 15:30:00');
        
        INSERT INTO "InviteEvent" ("objectId", "member", "type", "click", "share", "count") 
        VALUES 
        ('101', '1', '邀请注册', '10', '5', 1),
        ('102', '2', '邀请注册', '8', '3', 1),
        ('103', '3', '邀请注册', '6', '2', 1),
        ('104', '4', '邀请注册', '5', '1', 1),
        ('105', '5', '邀请注册', '4', '1', 1);
        `
        const data = await db.any(sql);
        console.log(data)
        return data;
        } catch (error) {
        console.error('Error executing SELECT query:', error);
        return null;
        }
        }
        
        // 调用函数并处理结果
        main()
        
    • 执行脚本,创建数据表插入测试数据

      node ./db/sql-create.js
      

    编写邀请关系SQL语句并测试

    • 根据上述的:db/sql-create.js脚本
      • 创建新脚本:db/sql-invite.js

    提示词:如上述Member表所示,其中invitePath是字符串数组,每个元素就是ID字符串,请帮用any比较invitePath数组中,包含该所提供的ID字符串的所有用户列表。

     // 查询:Member数量查询
    const data1 = await db.any(`SELECT COUNT(1) FROM "Member";`);
    console.log(`Member数量:`,data1)
    
    // 查询:InviteEvent数量查询
    const data2 = await db.any(`SELECT COUNT(1) FROM "InviteEvent";`);
    console.log(`InviteEvent数量:`,data2)
    
    // 查询:邀请关系会员列表查询
    let inviteId = "2"
    const data3 = await db.any(`SELECT * FROM "Member" WHERE "invitePath" ?& $1`,[[inviteId]]);
    console.log(`邀请关系会员列表:`,data3)
    
    

    API开发

    HTTP服务及API描述生成

    提示词:如上述的接口描述和SQL语句,请你帮我用express和pg-promise实现API服务,包含以上几个接口,并用apidoc来注释每个接口。

    当然可以帮你实现这个API服务,以下是一个基于Express和pg-promise的示例代码:

    // 引入所需模块
    const express = require('express');
    const bodyParser = require('body-parser');
    const pgp = require('pg-promise')();
    const db = pgp('postgresql://web3:666@web2023.fmode.cn:25432/dev');
    
    // 创建Express应用
    const app = express();
    app.use(bodyParser.json());
    
    // 会员账号注册接口
    /**
     * @api {post} /register 会员账号注册接口
     * @apiName Register
     * @apiGroup Member
     *
     * @apiParam {String} mobile 手机号
     * @apiParam {String} code 验证码
     * @apiParam {String} inviteId 邀请人ID
     *
     * @apiSuccess {Object} data 注册结果信息
     */
    app.post('/register', async (req, res) => {
        // 实现注册逻辑
    });
    
    // 建立邀请关系接口
    /**
     * @api {get} /invite 建立邀请关系接口
     * @apiName Invite
     * @apiGroup Invite
     *
     * @apiParam {String} inviterId 邀请人ID
     * @apiParam {String} inviteeId 被邀请人ID
     *
     * @apiSuccess {Object} data 建立邀请关系结果信息
     */
    app.get('/invite', async (req, res) => {
        // 实现建立邀请关系逻辑
    });
    
    // 邀请数据统计接口
    /**
     * @api {get} /stats 邀请数据统计接口
     * @apiName Stats
     * @apiGroup Invite
     *
     * @apiParam {String} userId 当前用户ID
     *
     * @apiSuccess {Object} data 统计数据
     */
    app.get('/stats', async (req, res) => {
        // 实现统计数据逻辑
    });
    
    // 邀请用户列表接口
    /**
     * @api {get} /userlist 邀请用户列表接口
     * @apiName UserList
     * @apiGroup Invite
     *
     * @apiParam {String} userId 当前用户ID
     *
     * @apiSuccess {Object} data 用户列表信息
     */
    app.get('/userlist', async (req, res) => {
        // 实现用户列表逻辑
    });
    
    // 启动Express服务器
    const PORT = 3000;
    app.listen(PORT, () => {
        console.log(`Server is running on port ${PORT}`);
    });
    

    在上面的代码中,你需要根据实际情况完善每个接口的具体逻辑。 在代码中,我已经为每个接口使用了apidoc来进行了简单的注释,你可以使用apidoc工具生成API文档。如果你需要更详细的实现或有其他问题,欢迎随时向我提问。

    单独生成每个接口(以注册为例)

    提示词: <复制会员账号注册接口描述> <复制Member表CREATE语句> 请你根据会员账号注册接口描述,和Member表结构,完成/register接口具体开发,其中数据库操作用pg-promise库。

    
    // 会员账号注册接口
    /**
     * @api {post} /register 会员账号注册接口
     * @apiName Register
     * @apiGroup Member
     *
     * @apiParam {String} mobile 手机号
     * @apiParam {String} code 验证码
     * @apiParam {String} inviteId 邀请人ID
     *
     * @apiSuccess {Object} data 注册结果信息
     */
     app.post('/register', async (req, res) => {
        const { mobile, code, inviteId } = req.body;
    
        // 验证请求参数
        if (!mobile || !code || !inviteId) {
            return res.status(400).json({ error: '缺少必要参数' });
        }
    
        try {
            // 查询邀请人信息
            const inviteMember = await db.oneOrNone('SELECT * FROM "Member" WHERE "objectId" = $1', inviteId);
    
            if (!inviteMember) {
                return res.status(400).json({ error: '邀请人不存在' });
            }
    
            // 插入新会员信息
            const newMember = await db.one(
                'INSERT INTO "Member" ("objectId", "mobile", "registerDate", "invite", "invitePath", "inviteDate") VALUES ($1,$2,$3,$4,$5,$6) RETURNING *',
                [mobile, mobile, new Date(), inviteId, null, null]
            );
    
            res.json({ data: newMember });
        } catch (error) {
            console.error('注册失败:', error);
            res.status(500).json({ error: '注册失败' });
        }
    });
    
    

    单独生成每个接口测试用例(以注册为例)

    测试脚本 test.js

    • 创建新的测试脚本 /test.js

      // 测试注册接口
      console.log("测试函数:邀请人不存在的情况")
      testRegister({
      mobile: '1234567890',
      code: '123456',
      inviteId: '123'
      });
      
      console.log("测试函数:邀请人存在的情况")
      testRegister({
      mobile: '1234567890',
      code: '123456',
      inviteId: '1'
      });
      
      
      async function testRegister(jsonBody) {
      const requestBody = jsonBody;
      
      try {
          const response = await fetch('http://localhost:3000/register', {
              method: 'POST',
              headers: {
                  'Content-Type': 'application/json',
              },
              body: JSON.stringify(requestBody),
          });
      
          const data = await response.json();
          console.log(data);
      } catch (error) {
          console.error('测试注册接口失败:', error);
      }
      }
      
      

    执行测试脚本

    • 注册接口测试通过

      node test.js
      
      测试函数:邀请人不存在的情况
      { error: '邀请人不存在' }
      测试函数:邀请人存在的情况
      {
      data: {
      objectId: '1234567890',
      name: null,
      mobile: '1234567890',
      registerDate: '2024-06-16T09:04:35.431Z',
      invite: '1',
      invitePath: null,
      inviteDate: null
      }
      }
      

    技术文档

    APIDOC配置和生成

    • 根据APIDOC,生成完整API文档

    提示词:我用apidoc描述了node/express项目,请问我该如何生成api文档?

    要生成API文档,您可以使用apidoc提供的命令行工具来生成静态HTML文档。以下是生成API文档的步骤:

    1. 首先,确保您已经在项目的根目录下安装了apidoc包。如果没有安装,您可以使用以下命令进行安装:

      npm install apidoc --save-dev
      
    2. 在您的项目中创建一个名为apidoc.json的配置文件,用于配置apidoc生成API文档的参数。您可以根据您的项目需求进行配置,以下是一个简单的示例:

      {
      "name": "会员邀请注册API",
      "version": "1.0.0",
      "description": "记录会员邀请关系,查询会员邀请列表",
      "title": "邀请注册API",
      "url" : "http://localhost:3000",
      "sampleUrl": "http://localhost:3000"
      }
      
    3. 在您的项目中编写好注释,并在您的代码中使用apidoc的注释格式来描述API接口。例如:

      /**
      * @api {get} /user/:id Request User information
      * @apiName GetUser
      * @apiGroup User
      *
      * @apiParam {Number} id User's unique ID.
      *
      * @apiSuccess {String} name User's name.
      * @apiSuccess {String} email User's email.
      */
      
    4. 运行以下命令来生成API文档:

      npx apidoc -i ./ -o docs/
      

    其中,<input_dir>是您存放API文档注释的目录,<output_dir>是生成的API文档的输出目录。

    1. 完成后,您会在指定的输出目录中看到生成的API文档。您可以通过浏览器打开index.html文件来查看您的API文档。

    希望这些步骤能帮助您成功生成API文档!如果您有任何其他问题,请随时告诉我。

    APIDOC接口文档预览