个人资料共同点
- 不同类型(如字符串, 数组)的属性
- 字段过滤
保证在请求像用户列表这种体量大的字段的接口, 不会返回全部的属性
否则有 100 万用户, 返回100 万的数组, 数组里每一项都是巨大的 json, 那么这种接口是不合理的. 因此要实现字段过滤
个人资料的 Schema 设计
mongodb 是 schema free, 是 nosql
这里的 schema 是 json 的数据结构, 因为 mongodb 是文档数据库, 文档看做 json
为了方便存储各个类型的数据, 要设计 json 的结构, 也就是 schema 设计
- 分析个人资料的数据结构
- 代码形式把 schema 写入项目
const userSchema = new Schema({
__v: { type:Number, select:false },
name: { type: String, required: true },
password: { type: String, required: true, select: false },
// 头像字段: 数据库存储图片保存一段地址
avatar_url: { type: String },
// 性别字段: 可以存成布尔值,但是不语义化. 而且除了男就是女, 所以是个可枚举的字符串
// mongoose 有一个语法描述可枚举, enum是可枚举的简写, default 默认值
gender: { type: String, enum: ['male','female'], default:'male', required: true },
// 个人签名字段:
headline: { type: String },
// 居住地字段: 是一个数组, 数组里每一项是个字符串
location: { type: [{ type:String }] },
// 职业经历字段: 它的 type 是数组, 数组里是对象
employments: {
type: [{
company: { type: String },
job: { type: String }
}]
},
// 教育经历字段:
educations: {
type: [{
school: { type:String },
major: { type:String },
diploma: { type: Number, enum:[1,2,3,4,5] },
entrance_year: {type: Number},
graduation_year: {type: Number}
}]
}
})
个人资料的参数校验
参数校验也可以说是请求体校验
新建和更新的时候请求体 body 里放大量参数, 对这些参数进行校验
- 分析个人资料的数据结构
- 编写代码校验个人资料参数(写在更新用户接口里)
ctx.verifyParams({
name: { type:'string', required: false },
password: { type:'string', required: false },
//更新用户这个接口没有哪个字段必选, 必选可以放路由参数上
avatar_url: { type: 'string', required: false},
gender: { type: 'string', required: false},
headline: { type: 'string', required: false},
// itemType 代表数组里每一项是字符串类型
location: { type: 'array', itemType:'string',required: false},
// 对基本类型做校验即可, 数据库层也会做校验
employments: { type: 'array', itemType:'object', required: false},
educations: { type: 'array', itemType:'object', required: false},
});
接口提取出的数据在前端页面是以表单的形式, 最后前端工程师从表单里提取完这些结构之后,
整理成 json,通过 patch 请求发给后端
RESTful api 最佳实践–字段过滤
字段过滤: 在请求某特定对象接口时, 比如特定用户接口, 可以通过查询字符串上的一些字段来选择性的显示该用户有哪些字段显示, 哪些不显示.
- 重新设计 schema, 默认隐藏部分字段
特别是在用户列表接口
const userSchema = new Schema({
__v: { type:Number, select:false },
name: { type: String, required: true },
password: { type: String, required: true, select: false },
avatar_url: { type: String },
gender: { type: String, enum: ['male','female'], default:'male', required: true },
headline: { type: String },
// 默认不显示就是加上 select 选项
location: { type: [{ type:String }], select: false },
//下面的同理
- 通过查询字符串显示隐藏字段
问号后面的
比如:
http://localhost:端口号/users/id?fields=locations;employments
此时通过查询列表接口,已经默认不显示了.
但是通过获取特定用户接口,显示出的也是被隐藏掉的.
我们期望通过查询字符串来选择性的显示部分字段, 比如 locations 和 employments
这部分逻辑写到 findById 这种查询特定用户的控制器里
async findById(ctx) {
// 先获取查询字符串里的 fields 字段
// 这样获取的是以分号隔开的
const {fields} = ctx.query;
// mongoose 语法里可以通过 select 添加, 但是这样是写死的
// const user = await User.findById(ctx.params.id).select('+employments+locations')
// 要把 fields 以分号隔开的字符串自动转为这种形式
const selectFields = fields.split(';').filter(item => item).map(item => ' +' + item).join('')
// 上面这行代码首先用 split 以分号分割成数组,
// map 方法遍历其中每一项再变成空格加号加上每一项的数组
// 再用 join 方法把数组拼在一起. filter 方法用来过滤,让 item 必须存在,而避免是空格
const user = await User.findById(ctx.params.id).select(selectFields)
if(!user) { ctx.throw(404,'用户不存在')}
ctx.body = user;
}
-------------------------------
关注与粉丝功能
- 关注, 取消关注
- 获取关注人, 粉丝列表(用户-用户多对多关系)
Schema 设计
ctx.verifyParams({
//添加 following 字段
//Schema 是从 mongoose 引入的一个类
//Schema.types.objectid
floolowing: {
type:[{ type:Schema.Types.ObjectId, ref: 'User' }],
select: false,
}
RESTful 风格的关注与粉丝接口
- 实现获取关注人和粉丝列表接口
新加控制器
先获取某用户,通过 id 获取关注者列表,
通过 populate 方法 following 字段得到具体用户信息
if 判断
下面略
- 实现关注和取消关注接口
略
编写校验用户存在与否的中间件
async checkUserExist(ctx) {
// 关注和取消关注都把 id 放到了路由参数 params.id 上了
const user = await User.findById(ctx.params.id);
// 上面就拿到了用户
if(!user) { ctx.throw(404, '用户不存在')}
// 如果用户存在就向下执行其他中间件
await next()
}
在 router.put('/following/:id', auth, checkUserExist,follow)这样的路由里添加上
这样判断id 是否存在通过了就继续执行后面的, 不通过就在这结束
-------------------------------
话题模块
功能点:
- 话题的增改查
- 分页, 模糊搜索
- 用户属性中的话题引用
比如居住地, 行业, 学校等 都是话题
- 关注/取消关注话题, 用户关注的话题列表
话题接口设计
- 设计 Schema
- 实现 RESTful 风格的增改查接口
RESTful API 最佳实践 – 分页
通过 querystring, 也就是?后面的参数指定当前第几页, 根据这些来限制列表的返回值
实现分页逻辑
接口中添加逻辑
//mongoose 有个语法 limit, 代表只返回 10 项,
//skip,跳过前 10 项,从第 11 项开始返回 10 项
//下面这行代码代表 返回第二页,每一页有 10 项
//ctx.body = await Topic.find().limit(10).skip(10);
所以先写一下 limit 如何得到:
const page = Math.max(ctx.query.page * 1,1) - 1);
const perPage = Math.max(ctx.query.per_page * 1, 1);
ctx.body = await Topic.find().limit(perPage).skip(page * perPage);
RESTful API 最佳实践 – 模糊搜索
模糊搜索也可以叫关键词搜索, 比如说在接口的 querystring 后面加上 q=“关键字”,
那么查找的列表就经过关键词的过滤, 就是搜索功能
ctx.body = await Topic
.find({name:'精确'}) //这样是精确搜索
//mongoose 提供了模糊搜索的正则
//关键词从 querystring 获得
.find({ name: new RegExp(ctx.query.keyword) })
问题模块
questioner: { type: schema.Types.Objected, ref:'User', required:true , select: false}