项目中通常使用 RESTful API,实现对第三方提供API服务。RESTful API 使用 HTTP 中的请求类型来标识对资源的增删改查的操作。如:
|Method|URL|说明|
|---|---|---|
|GET| /user|获取 user列表|
|GET| /user/:id|查看某个具体的 user|
|POST | /user| 新建一个 user|
|PUT| /user/:id| 更新指定 id 值的 user|
|DELETE |/user/:id|删除指定 id 值的 user|
# 创建 RESTful Controller
可以通过`-r`参数来创建 REST Controller。如:
~~~
thinkjs controller user -r
~~~
会创建下面几个文件:
~~~
create : src/controller/rest.js
create : src/controller/user.js
create : src/logic/user.js
~~~
其中`src/controller/user.js`会继承`src/controller/rest.js`类,`rest.js`是 RESTful Controller 的基类,具体的逻辑可以根据项目情况进行修改。
# 添加自定义路由
RESTful Controller 创建后并不能立即对其访问,需要添加对应的自定义路由,修改路由配置文件`src/config/router.js`,添加如下的配置:
~~~js
module.exports = [
[/\/user(?:\/(\d+))?/, 'user?id=:1', 'rest'],
]
~~~
上面自定义路由的含义为:
* `/\/user(?:\/(\d+))?/`URL 的正则
* `user?id=:1`映射后要解析的路由,:1 表示取正则里的 (\\d+) 的值
* `rest`表示为 REST API
通过自定义路由,将`/user/:id`相关的请求指定为 REST Controller,然后就可以对其访问了。
* `GET /user`获取用户列表,执行`getAction`
* `GET /user/:id`获取某个用户的详细信息,执行`getAction`
* `POST /user`添加一个用户,执行`postAction`
* `PUT /user/:id`更新一个用户,执行`putAction`
* `DELETE /user/:id`删除一个用户,执行`deleteAction`
如果有一系列路由都是 RESTful 路由的话,每次都添加自定义路由势必有些麻烦,这时候可以修改一下自定义路由的配置文件,例如:
~~~js
module.exports = [
[/\/api\/(\w+)(?:\/(\d+))?/, 'api/:1?id=:2', 'rest']
];
~~~
这样表示所有以`/api`开头的二级路由都会被指定成 RESTful 路由。
# 实例操作
创建school的REST控制器
~~~
thinkjs controller school -r
~~~
会创建下面几个文件:
~~~
create : src/controller/rest.js
create : src/controller/school.js
create : src/logic/school.js
~~~
## rest.js文件
文件路径:src/controller/rest.js
> 已经修改默认的getAction实现,增加了分页查找的支持
```js
const assert = require('assert');
module.exports = class extends think.Controller {
static get _REST() {
return true;
}
constructor(ctx) {
super(ctx);
this.resource = this.getResource();
this.id = this.getId();
assert(think.isFunction(this.model), 'this.model must be a function');
this.modelInstance = this.model(this.resource);
}
__before() { }
/**
* get resource
* @return {String} [resource name]
*/
getResource() {
return this.ctx.controller.split('/').pop();
}
getId() {
const id = this.get('id');
if (id && (think.isString(id) || think.isNumber(id))) {
return id;
}
const last = this.ctx.path.split('/').slice(-1)[0];
if (last !== this.resource) {
return last;
}
return '';
}
async getAction() {
let data;
if (this.id) {
const pk = this.modelInstance.pk;
data = await this.modelInstance.where({ [pk]: this.id }).find();
return this.success(data);
}
const page = this.get('page'); //分页参数
if (page) {
const pageSize = this.get('pageSize') || 10; //分页参数
data = await this.modelInstance.page(page, pageSize).countSelect();
return this.success(data);
} else {
data = await this.modelInstance.select(); //不分页,检索全部记录
return this.success(data);
}
}
/**
* put resource
* @return {Promise} []
*/
async postAction() {
const pk = this.modelInstance.pk;
const data = this.post();
delete data[pk];
if (think.isEmpty(data)) {
return this.fail('data is empty');
}
const insertId = await this.modelInstance.add(data);
return this.success({ id: insertId });
}
/**
* delete resource
* @return {Promise} []
*/
async deleteAction() {
if (!this.id) {
return this.fail('params error');
}
const pk = this.modelInstance.pk;
const rows = await this.modelInstance.where({ [pk]: this.id }).delete();
return this.success({ affectedRows: rows });
}
/**
* update resource
* @return {Promise} []
*/
async putAction() {
if (!this.id) {
return this.fail('params error');
}
const pk = this.modelInstance.pk;
const data = this.post();
data[pk] = this.id; // rewrite data[pk] forbidden data[pk] !== this.id
if (think.isEmpty(data)) {
return this.fail('data is empty');
}
const rows = await this.modelInstance.where({ [pk]: this.id }).update(data);
return this.success({ affectedRows: rows });
}
__call() { }
};
```
## 重载getAction
修改 src/controller/school.js文件
* [ ] 增加按照关键字模糊匹配
* [ ] 增加根据学校代码查找的方法接口
```js
const BaseRest = require('./rest.js');
module.exports = class extends BaseRest {
async getAction() {
let data;
if (this.id) {
const pk = this.modelInstance.pk;
data = await this.modelInstance.where({ [pk]: this.id }).find();
return this.success(data);
}
const page = this.get('page') || 1;
const pageSize = this.get('pageSize') || 10;
const method = this.get('method');
if (method === 'getSchoolByCode') { //使用学校代码检索
const school_code = this.get('school_code');
data = await this.modelInstance.where({ school_code: school_code }).find();
// think.logger.debug(data)
return this.success(data);
}
let map = {};
const key = this.get('key');
if (key) {
map['title|school_code'] = ['LIKE', '%' + key + '%']; //title LIKE '%KEY%' OR school_code LIKE '%KEY%'
}
const page = this.get('page'); //分页参数
if (page) {
const pageSize = this.get('pageSize') || 10; //分页参数
data = await this.modelInstance.where(map).page(page, pageSize).countSelect();
return this.success(data);
} else {
data = await this.modelInstance.where(map).select(); //不分页,检索全部记录
return this.success(data);
}
}
};
```
## 修改路由配置文件
src/config/router.js
```js
module.exports = [
[/\/school(?:\/(\d+))?/, 'school?id=:1', 'rest'], // 第一种方式
];
```
## 调用方法
| Method | 调用实例 |
| --- | --- |
| GET | http://localhost:8360/school/1 |
| GET | http://localhost:8360/school?page=1&pageSize=10 |
| GET | http://localhost:8360/school?method=getSchoolByCode&school_code=12046 |
| PUT |http://localhost:8360/school/1 |
| POST|http://localhost:8360/school/1 |
| DELETE|http://localhost:8360/school/1 |
> PUT和POST操作需要设置POST的内容,使用JSON格式
## 批量添加模拟数据
需要安装并引入mockjs包
```js
const Mock = require('mockjs');
```
执行POST请求
```
POST http://127.0.0.1:8360/index/mock
```
```js
async mockAction() {
if (this.isPost) {
//生成模拟数据
let mockOption = {
'data|5': [
{
//'id': '@increment()',
'school_code': '12046', //学校代码
'title|1': ['中山大学', '北京大学', '清华大学', '广州大学', '华南理工大学', '国防科技大学', '广州番禺职业技术学院'], //学校名称
'type|1': ['本科', '高职高专', '一般大专', '中专'], //学校类型
'city': '@city(true)', //学校所在城市
'address': '@county(true)', //学校地址
'cover_image_id': 1,
'cover_image': 'http://via.placeholder.com/200x150', //公司LOGO
'certified|1': ['认证学校', ''],
'description': '@cparagraph(10,20)', //公司介绍
'tags': function () {
//随机选择3个标签
let array = ['双一流', '985', '示范性院校', '教育部直属', '民办高校'];
array.sort(function (a, b) {
return Math.random() > 0.5 ? 1 : 0;
}); //简单打乱方法
let [a, b, c, ...rest] = array;
return [a, b, c].join(',');
},
}],
};
let data = await Mock.mock(mockOption);
//删除已有的数据
await this.model('school').where(true).delete();
//添加新的模拟数据
let school_code = 12046;
for (let item of data.data) {
item.school_code = school_code++;
await this.model('school').where({ "title": item.title }).thenAdd(item);
}
data = await this.model('school').select();
return this.success(data);
}
}
```
- 内容介绍
- EcmaScript基础
- 快速入门
- 常量与变量
- 字符串
- 函数的基本概念
- 条件判断
- 数组
- 循环
- while循环
- for循环
- 函数基础
- 对象
- 对象的方法
- 函数
- 变量作用域
- 箭头函数
- 闭包
- 高阶函数
- map/reduce
- filter
- sort
- Promise
- 基本对象
- Arguments 对象
- 剩余参数
- Map和Set
- Json基础
- RegExp
- Date
- async
- callback
- promise基础
- promise-api
- promise链
- async-await
- 项目实践
- 标签系统
- 远程API请求
- 面向对象编程
- 创建对象
- 原型继承
- 项目实践
- Classes
- 构造函数
- extends
- static
- 项目实践
- 模块
- import
- export
- 项目实践
- 第三方扩展库
- immutable
- Vue快速入门
- 理解MVVM
- Vue中的MVVM模型
- Webpack+Vue快速入门
- 模板语法
- 计算属性和侦听器
- Class 与 Style 绑定
- 条件渲染
- 列表渲染
- 事件处理
- 表单输入绑定
- 组件基础
- 组件注册
- Prop
- 自定义事件
- 插槽
- 混入
- 过滤器
- 项目实践
- 标签编辑
- 移动客户端开发
- uni-app基础
- 快速入门程序
- 单页程序
- 底部Tab导航
- Vue语法基础
- 模版语法
- 计算属性与侦听器
- Class与Style绑定
- 样式与布局
- Box模型
- Flex布局
- 内置指令
- 基本指令
- v-model与表单
- 条件渲染指令
- 列表渲染指令v-for
- 事件与自定义属性
- 生命周期
- 项目实践
- 学生实验
- 贝店商品列表
- 加载更多数据
- 详情页面
- 自定义组件
- 内置组件
- 表单组件
- 技术专题
- 状态管理vuex
- Flyio
- Mockjs
- SCSS
- 条件编译
- 常用功能实现
- 上拉加载更多数据
- 数据加载综合案例
- Teaset UI组件库
- Teaset设计
- Teaset使用基础
- ts-tag
- ts-badge
- ts-button
- ta-banner
- ts-list
- ts-icon
- ts-load-more
- ts-segmented-control
- 代码模版
- 项目实践
- 标签组件
- 失物招领客户端原型
- 发布页面
- 检索页面
- 详情页面
- 服务端开发技术
- 服务端开发环境配置
- Koajs快速入门
- 快速入门
- 常用Koa中间件介绍
- 文件上传
- RestfulApi
- 一个复杂的RESTful例子
- 使用Mockjs生成模拟数据
- Thinkjs快速入门
- MVC模式
- Thinkjs介绍
- 快速入门
- RESTful服务
- RBAC案例
- 关联模型
- 应用开发框架
- 服务端开发
- PC端管理界面开发
- 移动端开发
- 项目实践
- 失物招领项目
- 移动客户端UI设计
- 服务端设计
- 数据库设计
- Event(事件)
- 客户端设计
- 事件列表页面
- 发布页面
- 事件详情页面
- API设计
- image
- event
- 微信公众号开发
- ui设计规范