README

申而论之小程序服务端(Node + Express)

20201124-申而论之小程序

项目目录结构

本项目初始目录结构使用官方express-generator@4生成:

目录 说明
bin/ 启动脚本
public/ 静态文件
routes/ 路由
views/ 页面模板

额外加入的目录

目录 说明
utils/ 通用模块
db/ 数据库相关

额外加入的文件

文件 说明
api.yaml API描述文件(OpenAPI 3.0)
config.yaml 默认配置文件
const.yaml 常量配置文件

相关环境变量

通用配置

字段名 默认值 说明
SERVER_SECRET serversecret 用于各种加密的密钥
STATIC_USERS admin:admin 静态用户配置

MySQL配置:

字段名 默认值 说明
MYSQL_HOST localhost 地址
MYSQL_PORT 3306 端口
MYSQL_USER dev 用户名
MYSQL_PASSWORD dev 密码
MYSQL_DATABASE proj_shenlun 数据库名
MYSQL_CHARSET utf8mb4 编码
MYSQL_TIMEZONE +08:00 时区
MYSQL_CONNECTION_LIMIT 100 连接池大小
MYSQL_LOG_SQL false 是否在日志中输出SQL语句

Session/Token配置

字段名 默认值 说明
COOKIE_SESSION_NAME sessionid 保存Session ID 的Cookie 名
COOKIE_SESSION_EXPIRES 86400 Session 过期时间(秒)
JWT_EXPIRES 86400 JWT 过期时间(秒)

其他配置

字段名 默认值 说明
PAGE_SIZE 20 默认分页大小

相关命令

启动后默认访问端口为3000

# 启动服务
npm start

# 启动服务(输出DEBUG信息)
DEBUG=server-node-express:* npm start

后台服务说明

Gitee项目地址:shen-lun-m-p/api-service

[2022-08-13] 缩减本文档部分内容

1. 一般性规则

  1. 所有业务实体都有唯一名称/英文名(见下表)
  2. 业务实体字段名在本系列接口中为原名,在其他业务实体系列中为业务实体名+字段名

    (如:「用户」的英文名为user,那么,用户ID在用户列表为id,而在其他列表中的关联用户ID则为userId

  3. ishas开头的字段都是布尔值(如:isDisabled
  4. 列表接口可以指定排序参数,填写字段名表示排序,默认为正序,字段名前加减号-表示倒序

    (如:sort=name表示以name字段正序排列,sort=-name表示以name字段倒序排列)

  5. 所有业务实体默认都存在以下固定字段,这些字段与业务并不直接关联(见下表)
  6. 接口返回内容中,可通过isOK简单判断成功与否
  7. 所有列表类接口的过滤条件,如没有特别说明,都是精确匹配(字段 = 值
  8. 所有列表类接口的过滤条件,可以使用_NULL表示按NULL匹配(字段 IS NULL
  9. 所有列表类接口的过滤条件,可以使用_NOT_NULL表示按NOT NULL匹配(字段 IS NOT NULL

[2020-12-28新增] 9. 所有列表类接口都会自动生成对应的根据ID筛选的详情接口,两接口本质上是同一个接口。但详情接口查询无数据时,默认会返回404,可指定?no404=true取消404报错 10. 自动生成的详情接口都排在每一组接口的最后,且在标题/描述中写明是一个自动生成的接口 11. 列表接口可以指定返回字段,填写字段名表示仅返回指定的字段(如:?fields=field1,field2,英文逗号分隔,中间无空格);开头增加减号表示剔除指定的字段(如:?fields=-,field1,field2?fields=-field1,field2

[2021-02-06新增] 12. 所有接口的请求体、响应体中出现的字段名以JSON结尾(如: extraJSON)的字段,全程认为object类型处理即可。服务器端都会自动做序列化反序列化处理(即入库自动序列化,出库自动反序列化)

2. 本API页面登录方式:

  1. 调用POST /api/sign-in接口,复制响应体中,data.jwt字段的内容作为令牌
  2. 点击下方右侧的Authorize按钮,将令牌值填入AdminBearerAuth中,点击Authorize即可

3. 前端登录方式:

  1. 调用POST /api/sign-in接口,保存响应体中,data.jwt字段的内容作为令牌
  2. 对于浏览器访问,调用POST /api/sign-in接口即可,令牌同时会加入到Cookies中
  3. 对于应用调用接口时,请求头中加入:Authorization: Bearer {令牌}即可实现认证
  4. 对于微信小程序登录,需要调用POST /api/wx-mini/sign-in?code={调用wx.login()获取的临时登录凭证}获取令牌,后续操作与上述3.相同
    • 微信小程序登录成功后,小程序可以调用POST /api/me/profile,将当前用户信息保存至服务端,供后台管理使用

[2021-08-24新增] 5. 对于使用第三方登录时(如:小程序登录),如果未绑定手机号,则不会直接返回令牌,而是返回临时Code。并使用此临时Code调用POST /api/mobile/bind绑定手机。绑定成功后返回令牌。 6. 对于使用第三方登录时(如:小程序登录),如果已经绑定手机号,则直接返回令牌。 7. 使用手机号登录,在测试环境中,正确输入验证码或者固定输入000000都可以验证通过,手机登录时,新用户会自动创建

4. 媒体库设计

媒体库用于存储业务实体的「附件」,业务实体操作接口本身并不直接提供媒体库的操作功能。

如:为主题贴thread附带图片的处理流程如下:

  1. 创建主题贴,获得主题贴ID(假设id="thrd-001"
  2. 调用POST /api/media接口上传图片,并指定bizId值为主题贴ID(即bizId="thrd-001"

至此,在后续查询主题贴列表/详情时,会自动附带media字段(类型为数组,无数据时为空数组),包含字段如下:

"media": [
  {
    "seq"         : <媒体序号>,
    "id"          : "<媒体ID>",
    "userId"      : "<上传用户ID>",
    "bizId"       : "<业务实体ID>",
    "originalname": "<原始文件名>",
    "mimetype"    : "<文件类型,如:image/jpeg>",
    "size"        : <文件大小,字节>,
    "publicURL"   : "<文件公网下载地址>"
  }
]

上传附件支持两种模式:

多附件的业务实体可以使用追加模式;单附件(如封面)的业务实体可以使用替换模式

5. 极光推送

系统在调用极光推送Push接口时,会以以下方式推送:

{
    "platform": "all",
    "audience": "<接收者,详细见下文>",

    "notification": {
        "alert": "<消息文案,详细见下文>",

        "android": {
            "extras": {
                "reason": "<推送原因,详细见下文>",
                "xxxxId": "<关联业务实体ID>"
            }
        },

        "ios": {
            "badge": "+1",
            "extras": {
                "reason": "<推送原因,详细见下文>",
                "xxxxId": "<关联业务实体ID>"
            }
        }
    }
}

新题提醒

{
  "reason"    : "questionPublished",
  "module"    : "<writing|interview>",
  "questionId": "<题目ID>",
  "paperId"   : "<试卷ID>",
}

题目评论回复提醒

{
  "reason"                   : "questionCommentReply",
  "module"                   : "<writing|interview>",
  "questionId"               : "<题目ID>",
  "questionCommentId"        : "<题目评论ID>",
  "questionCommentReplyId"   : "<回复的题目评论ID>",
  "questionCommentTopReplyId": "<顶层题目评论ID>",
}

讨论发帖回复提醒

{
  "reason"        : "postReply",
  "threadId"      : "<主题ID>",
  "postId"        : "<发帖ID>",
  "postReplyId"   : "<回复的发帖ID>",
  "postTopReplyId": "<顶层发帖ID>",
}

热点资讯回复提醒

{
  "reason"                      : "hotspotNewsCommentReply",
  "module"                      : "<writing|interview>",
  "hotspotNewsId"               : "<热点资讯ID>",
  "hotspotNewsCommentId"        : "<热点资讯评论ID>",
  "hotspotNewsCommentReplyId"   : "<回复的热点资讯评论ID>",
  "hotspotNewsCommentTopReplyId": "<顶层热点资讯评论ID>",
}

课程开始报名提醒

满足以下条件时触发

  1. 当前时间已经超过「课程」的applyTime
  2. 用户设置了课程提醒,且尚未向此用户发送过报名提醒
{
  "reason"    : "courseApplyTimeReached",
  "module"    : "<writing|interview>",
  "courseId"  : "<课程ID>",
  "courseType": "<课程类型>"
}

课程开班提醒

满足以下条件时触发

  1. 当前时间已经超过「课程」的openTime
  2. 用户设置了课程提醒,且尚未向此用户发送过开班提醒
{
  "reason"    : "courseOpenTimeReached",
  "module"    : "<writing|interview>",
  "courseId"  : "<课程ID>",
  "courseType": "<课程类型>"
}

评论(通用版)收到评论提醒

满足以下条件时触发

  1. 调用「创建评论」接口时
  2. 且,上述调用数据指定bizType="dailyEventWork"
{
  "reason"            : "commentReceived",
  "module"            : "<writing|interview>",
  "bizType"           : "<提交评论时传递的bizType>",
  "bizId"             : "<提交评论时传递的bizId>",
  "commentId"         : "<新创建的 评论ID>",
  "dailyEventId"      : "<日常打卡ID      (仅在 bizType=dailyEventWork 时存在此字段)>",
  "dailyEventTaskId"  : "<日常打卡任务ID  (仅在 bizType=dailyEventWork 时存在此字段)>",
  "dailyEventTaskDate": "<日常打卡任务日期(仅在 bizType=dailyEventWork 时存在此字段)>",
}

评论(通用版)回复提醒

满足以下条件时触发

  1. 调用「创建评论」接口时
  2. 且,上述调用数据指定了replyId
{
  "reason"            : "commentReply",
  "module"            : "<writing|interview>",
  "bizType"           : "<提交评论时传递的bizType>",
  "bizId"             : "<提交评论时传递的bizId>",
  "commentId"         : "<新创建的 评论ID>",
  "commentReplyId"    : "<被评论的 评论ID>",
  "commentTopReplyId" : "<所属顶层 评论ID>",
  "courseType"        : "<课程类型        (仅在 bizType=course|courseSection|courseTask 时存在此字段)>",
  "dailyEventId"      : "<日常打卡ID      (仅在 bizType=dailyEventWork 时存在此字段)>",
  "dailyEventTaskId"  : "<日常打卡任务ID  (仅在 bizType=dailyEventWork 时存在此字段)>",
  "dailyEventTaskDate": "<日常打卡任务日期(仅在 bizType=dailyEventWork 时存在此字段)>",
}

日常打卡发布通知

满足以下条件时触发

  1. 调用「创建日常打卡」接口,且指定isPublished=true
  2. 或,调用「修改日常打卡」接口,将isPublished字段从false修改为true
{
  "reason"            : "dailyEventPublished",
  "module"            : "<writing|interview>",
  "dailyEventId"      : "<日常打卡ID>",
  "dailyEventCategory": "<日常打卡分类>",
}

日常打卡失去资格通知

满足以下条件时触发

  1. 用户在「日常打卡」中缺卡次数超过日常打卡设置的allowedAbsenceCount
{
  "reason"            : "rejectedFromDailyEvent",
  "module"            : "<writing|interview>",
  "dailyEventId"      : "<日常打卡ID>",
  "dailyEventCategory": "<日常打卡分类>",
}

日常打卡开始报名提醒

2022-10-31 版本

满足以下条件时触发

  1. 当前时间已经超过「日常打卡」的applyTime
  2. 用户设置了课程提醒,且尚未向此用户发送过报名提醒
{
  "reason"            : "dailyEventApplyTimeReached",
  "module"            : "<writing|interview>",
  "dailyEventId"      : "<日常打卡ID>",
  "dailyEventCategory": "<日常打卡分类>",
}

日常打卡开始通知

2022-10-31 版本

满足以下条件时触发

  1. 当前时间已经超过「日常打卡」的startTime - 1天
  2. 用户报名了此日常打卡,且尚未向此用户发送过开始提醒
{
  "reason"            : "dailyEventStartTimeReached",
  "module"            : "<writing|interview>",
  "dailyEventId"      : "<日常打卡ID>",
  "dailyEventCategory": "<日常打卡分类>",
}

日常打卡每日提醒通知

2022-10-31 版本

满足以下条件时触发

  1. 当前时间已经超过「日常打卡」的startTime,且超过当天大的noticeHM
  2. 用户状态为applied
{
  "reason"            : "dailyEventEverydayNotice",
  "module"            : "<writing|interview>",
  "dailyEventId"      : "<日常打卡ID>",
  "dailyEventCategory": "<日常打卡分类>",
}

写作打卡发布通知

满足以下条件时触发

  1. 调用「创建写作打卡」接口,且指定isPublished=true
  2. 或,调用「修改写作打卡」接口,将isPublished字段从false修改为true
{
  "reason"        : "writingEventPublished",
  "writingEventId": "<写作打卡ID>",
}

常用的命令

复制服务器目录到本地

scp -r root@120.55.62.189:/root/project/webhook-receiver ~/Workspace/YiXue

直播

包含字段

字段名 说明
id 直播ID
title 直播标题
subTitle 直播标题
type 直播类型:申论(shenlun)、行测(administrative)、面试(interview)
url 直播间地址
startTime 开始时间
endTime 结束时间
teacherId 主讲老师 ID
isPublished 是否发布

直播回放

字段名 说明
id 回放ID
title 回放标题
subTitle 回放标题
type 回放类型:直播类型:申论(shenlun)、行测(administrative)、面试(interview)
url 回放地址

首页推荐位

字段名 说明
id 推荐位ID
title 推荐位标题
type 推荐位类型:直播(live)、打卡(event)、课程(course)
bizId 推荐位对应的业务ID,类型为直播或打卡是单个 Id,类型为课程时是用逗号分割的 Id
isPublished 是否发布
publishTime 发布时间
priority 优先级,用于排序,数字越小越靠前

一些操作

重启数据库

1. docker 操作目录
cd ~/docker-stack/

2. 关闭 mysql
docker stack rm mysql

3. 确认 mysql 容器已经退出
docker stack ps

4. 开启 mysql
docker stack up mysql -c mysql.yaml

重启服务

pm2 kill; pm2 start pm2.json

重启ngnix

nginx -s reload

ssl证书更换

在阿里云购买证书,下载证书文件(Ngnix版本), 替换/root/ss文件夹下的证书文件,然后重启ngnix

更新隐私协议

替换目录/var/www/officialwebsite-new/

管理员账号

可进行退款操作的管理员账号:admin,密码:admin 账号:yx001 密码:yx666

重构一期内容

新增的数据库表

重构二期内容-地面班优化