項目中通常使用 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設計規范