When handling the user's request in Action, it is often necessary to obtain the user submit data and verify them. If there is no problem in the verification, the subsequent operations can be performed. Sometime it also check authorization, etc. If you put these codes in an Action, it is bound to make Action's code very complex and lengthy.
In order to solve this problem, ThinkJS adds a layer of Logic
in front of the controller. The Action in Logic corresponding with the Action in the controller. System will automatically invoke the Action in Logic before calling controller Action.
The Logic directory is located at src/[module]/logic
. Command thinkjs controller test
will create test controller and test logic.
Logic code similar to the following:
module.exports = class extends think.Logic {
__before() {
// todo
}
indexAction() {
// todo
}
__after() {
// todo
}
}
Note: Logic files and Controller file names must be the same if you create them manually
Among them, Action in Logic and action in Controller is one-to-one correspondence. Logic also supports __before
and__after
magic methods.
Sometimes a specific Action need to be limited to certain types of requests, and to reject other request types. You can filter the request by configuring a specific request type.
module.exports = class extends think.Logic {
indexAction() {
this.allowMethods = 'post'; // only POST
}
detailAction() {
this.allowMethods = 'get,post'; // allow GET or POST
}
}
Data validation configuration format is field
: JSON config object
, as follows:
module.exports = class extends think.Logic {
indexAction(){
let rules = {
username: {
string: true, // data type is String
required: true, // username field is required
default: 'thinkjs', // default value is 'thinkjs'
trim: true, // data will be trim
method: 'GET' // request method
},
age: {
int: {min: 20, max: 60} // integer between 20 to 60
}
}
let flag = this.validate(rules);
}
}
Supported data types including: boolean
, string
, int
, float
, array
and object
, one data field only allows one basic data type, default is string
.
Sometime it can't automatically get the value (such as: value from the header), then you can manually get the value and then set the configuration. Such as:
module.exports = class extends think.Logic {
saveAction(){
let rules = {
username: {
value: this.header('x-name') // get value from header
}
}
}
}
If you validate version
parameter, its value is obtained from version
field by specific request type. If the current request type isGET
, it will get version
Field; if the request type isPOST
, the value of the field is retrieved via this.post('version')
, and if the current request type is FILE
, then this.file('version')
to get the value.
Sometimes in the POST
type, you may get the uploaded file or get the parameters on the URL, this time you need to specify the way to get the data. Supported data acquisition methods are GET
, POST
, and FILE
.
module.exports = class extends think.Logic {
indexAction(){
let rules = {
username: {
required: true,
method: 'GET' // data acquisition methods
}
}
let flag = this.validate(rules);
}
}
Use default:value
to set a field's default value, if field value is empty, default value will be set and apply the following validation logic.
Use trim:true
if current field suports trim
operation.
After validation rule is configured, you can call this.validate
to validate. Such as:
module.exports = class extends think.Logic {
indexAction(){
let rules = {
username: {
required: true
}
}
let flag = this.validate(rules);
if(!flag){
return this.fail('validate error', this.validateErrors);
// return is validation is failed
// {"errno":1000,"errmsg":"validate error","data":{"username":"username can not be blank"}}
}
}
}
If you use this.isGet
orthis.isPost
in the controller's action to determine the request, you would also need to include the this.isGet
orthis.isPost
in the code above, for example:
module.exports = class extends think.Logic {
indexAction(){
if(this.isPost) {
let rules = {
username: {
required: true
}
}
let flag = this.validate(rules);
if(!flag){
return this.fail('validate error', this.validateErrors);
}
}
}
}
If the return value is false
, then you can get a detailed error message by this.validateErrors
. And you can output the error message in JSON
format via thethis.fail
method, or you can output a page via this.display
. Logic inherits all Controller methods.
In most cases, we just output JSON error message after validation failed. If you don't want call this.validate
manually each time, you can verify it automatically by setting the validation rule into this.rules
attribute. For example:
module.exports = class extends think.Logic {
indexAction(){
this.rules = {
username: {
required: true
}
}
}
}
Equivalent to
module.exports = class extends think.Logic {
indexAction(){
let rules = {
username: {
required: true
}
}
let flag = this.validate(rules);
if(!flag){
return this.fail(this.config('validateDefaultErrno') , this.validateErrors);
}
}
}
Assigning the validation rule to the this.rules
automatically validates the execution of the Action, and outputs an error message in JSON format if there is an error.
For multiple actions Sometimes we want to re-use some of the validation rules, for example indexAction
and homeAction
in logic
have to verify the app_id
field is required, you can move app_id
validation to scope
.
module.exports = class extends think.Logic {
get scope() {
return {
app_id: {
required: true
}
}
}
indexAction(){
let rules = {
email: {
required: true
}
}
// custom app_id error message
let msgs = {
app_id: '{name} can not be null',
}
if(!this.validate(rules, msgs)) {
return this.fail(this.validateErrors);
}
}
homeAction() {
// email shorthand
// app_id use default error message
this.rules = {
email: {
required: true
}
}
}
}
Data validation supports Array type, but it only supports simple arrays, and does not support nested arrays. children
specifies an identical check rule for all array elements.
module.exports = class extends think.Logic {
let rules = {
username: {
array: true,
children: {
string: true,
trim: true,
default: 'thinkjs'
},
method: 'GET'
}
}
this.validate(rules);
}
Data validation supports Object type, note nested arrays are not supported. children
specifies an identical check rule for all object attributes.
module.exports = class extends think.Logic {
let rules = {
username: {
object: true,
children: {
string: true,
trim: true,
default: 'thinkjs'
},
method: 'GET'
}
}
this.validate(rules);
}
For boolean
type field, value in one of 'yes'
,'on'
,'1'
,'true'
,true
will be converted to true
, and in other cases false
, and then performs the follow-up rule validation;
For array
type field, if the field itself is an array, it will not be processed. If the field is a string, it will be split (',')
, otherwise it will be directly converted into [field value]
, And then perform the follow-up rule validation.
For fields specified as int
or float
data type is automatically parseFloat
converted after the data is verified.
module.exports = class extends think.Logic {
indexAction(){
let rules = {
age: {
int: true,
method: 'GET'
}
}
let flag = this.validate(rules);
}
}
If there is an parameter of age=26
in url, typeof this.param('age') is of type number after logic level validation.
module.exports = class extends think.Logic {
indexAction(){
this.rules = {
username: {
required: true
}
}
}
}
For the above rules this.validateErrors would be {username: 'username can not be blank'} in the case of validation failure. But sometimes you want to customize the error to 'username can not be null'. Need to do the following:
First in src/config/validator.js
overwrite the default required
error message:
module.exports = {
messages: {
required: '{name} can not be null',
}
}
module.exports = class extends think.Logic {
indexAction(){
this.rules = {
username: {
required: true,
aliasName: 'username'
}
}
}
}
Create a validator.js
file under the config
directory in single-module project and validator.js
under common/config
directory in multi-module project. Add a custom verification method in validator.js
:
For example, we want to verify that the name1
parameter in GET
request is equal to the string lucy
. You can add a validation rule as follows; [server address]/index/?Name1=jack
// logic index.js
module.exports = class extends think.Logic {
indexAction() {
let rules = {
name1: {
eqLucy: 'lucy',
method: 'GET'
}
}
let flag = this.validate(rules);
if(!flag) {
console.log(this.validateErrors); // name1 shoud eq lucy
}
}
}
}
// src/config/validator.js
module.exports = {
rules: {
eqLucy(value, { argName, validName, validValue, ctx, currentQuery, rule, rules, parsedValidValue }) {
return value === validValue;
}
},
messages: {
eqLucy: '{name} should eq {args}'
}
}
Custom validation method parameters is as follow:
(
value: , // The value of the parameter in the corresponding request, here is ctx['param']['name1']
{
argName, // parameter name,here is name1
validName, // validation method name, here is 'eqLucy'
validValue, // validate value, here is 'lucy'
parsedValidValue, // return value of _eqLucy method, if _eqLucy is not provied, it is validValue
currentQuery, // curreny request query, equal to ctx['param']
ctx, // ctx object
rule, // validation rule, here is {eqLucy: 'lucy', method: 'GET'}
rules, // all validation rules, here is the value of let rules
}
)
Sometimes we want to parse the parameters of the validation rules, only need to create new parse method of the same name with an underscore at the beginning, and the results of the parse can be returned.
For example, if we want to verify that the name1
parameter in GET
request is equal to name2
parameter, we can add the following verification method: Visit [server address]/index/?name1=tom&name2=lily
// logic index.js
module.exports = class extends think.Logic {
indexAction() {
let rules = {
name1: {
eqLucy: 'name2',
method: 'GET'
}
}
let flag = this.validate(rules);
if(!flag) {
console.log(validateErrors); // name1 shoud eq name2
}
}
}
// src/config/validator.js
module.exports = {
rules: {
_eqLucy(validValue, { argName, validName, currentQuery, ctx, rule, rules }){
let parsedValue = currentQuery[validValue];
return parsedValue;
},
eqLucy(value, { argName, validName, validValue, parsedValidValue, currentQuery, ctx, rule, rules }) {
return value === parsedValidValue;
}
},
messages: {
eqLucy: '{name} should eq {args}'
}
}
The first parameter in _eqLucy
is the value of the current validation rule (for this example, the validValue is 'name2'), and the other parameters have the same meanings as above.
There are three interpolation variables {name}
, {args}
, {pargs}
in the error message. {name}
will be replaced by the name of the field being checked, {args}
will be replaced by the value of the checkout rule and {pargs}
will be replaced by the value returned by the parsing method. If {args}
, {pargs}
is not a string, JSON.stringify
will be processed.
For Object: false
fields, three custom error formats are supported: Rule1: Rule: Error Message
; Rule2: Field Name: Error Message
; Rule 3: Field Name: {Rule: Error Message}
.
For the case of multiple error messages specified at the same time, priority rule 3 > rule 2 > rule 1.
module.exports = class extends think.Logic {
let rules = {
username: {
required: true,
method: 'GET'
}
}
let msgs = {
required: '{name} can not be blank', // rule 1
username: '{name} can not be blank', // rule 2
username: {
required: '{name} can not be blank' // rule 3
}
}
this.validate(rules, msgs);
}
For Object: true
fields,it supports the following custom error message. priority si rule 5 > (4 = 3) > 2 > 1 .
module.exports = class extends think.Logic {
let rules = {
address: {
object: true,
children: {
int: true
}
}
}
let msgs = {
int: 'this is int error message for all field', // rule 1
address: {
int: 'this is int error message for address', // rule 2
a: 'this is int error message for a of address', // rule 3
'b,c': 'this is int error message for b and c of address' // rule 4
d: {
int: 'this is int error message for d of address' // rule 5
}
}
}
let flag = this.validate(rules, msgs);
}
required: true
field is required,default is required: false
. undefined
、[empty string]
、null
、NaN
will fail required: true
.
module.exports = class extends think.Logic {
indexAction(){
let rules = {
name: {
required: true
}
}
this.validate(rules);
// todo
}
}
name
is required.
This is required when the value of one item is one of some values. Such as:
module.exports = class extends think.Logic {
indexAction(){
let rules = {
name: {
requiredIf: ['username', 'lucy', 'tom'],
method: 'GET'
}
}
this.validate(rules);
// todo
}
}
For the above example, the value of name
is required when username
in GET
request is lucy
or tom
.
This is required when the value of one item is not one of some values. Such as:
module.exports = class extends think.Logic {
indexAction(){
let rules = {
name: {
requiredNotIf: ['username', 'lucy', 'tom'],
method: 'POST'
}
}
this.validate(rules);
// todo
}
}
For the above example, the value of name
is required when username
in GET
request is not lucy
or tom
.
This item is required when there is a value in several other items.
module.exports = class extends think.Logic {
indexAction(){
let rules = {
name: {
requiredWith: ['id', 'email'],
method: 'GET'
}
}
this.validate(rules);
// todo
}
}
For the above example, the value of name
is required when id
or email
in GET
request has value.
This item is required when several other values exist.
module.exports = class extends think.Logic {
indexAction(){
let rules = {
name: {
requiredWithAll: ['id', 'email'],
method: 'GET'
}
}
this.validate(rules);
// todo
}
}
For the above example, the value of name
is required when id
and email
in GET
request has value.
This item is required when there is a value that does not exist in several other items.
module.exports = class extends think.Logic {
indexAction(){
let rules = {
name: {
requiredWithOut: ['id', 'email'],
method: 'GET'
}
}
this.validate(rules);
// todo
}
}
For the above example, the value of name
is required when any of id
, email
does not exist inGET
request.
This value is required when several other values do not exist.
module.exports = class extends think.Logic {
indexAction(){
let rules = {
name: {
requiredWithOutAll: ['id', 'email'],
method: 'GET'
}
}
this.validate(rules);
// todo
}
}
For the above example, the value of name
is required when all the id
and email
values in GET
request do not exist.
The value needs to contain a specific value.
module.exports = class extends think.Logic {
indexAction(){
let rules = {
name: {
contains: 'ID-',
method: 'GET'
}
}
this.validate(rules);
// todo
}
}
For the above example, name
value in GET
request needs to container ID-
string.
equals to another filed.
module.exports = class extends think.Logic {
indexAction(){
let rules = {
name: {
equals: 'username',
method: 'GET'
}
}
this.validate(rules);
// todo
}
}
For the above example, name
value must be equal to username
value in GET
request.
not equal to another filed value.
module.exports = class extends think.Logic {
indexAction(){
let rules = {
name: {
different: 'username',
method: 'GET'
}
}
this.validate(rules);
// todo
}
}
For the above example, the values of name
and username
fields in GET
request are not equal.
The value needs to be before a date, by default it needs to be before the current date.
module.exports = class extends think.Logic {
indexAction(){
let rules = {
time: {
before: '2099-12-12 12:00:00', // before: true means before the current date.
method: 'GET'
}
}
this.validate(rules);
// todo
}
}
For the above example, time
field in GET
request needs to be before 2099-12-12 12:00:00
.
The value needs to be after a date, the default is after the current date, after: true | time string
.
The value can only be [a-zA-Z], alpha: true
.
The value can only be [a-zA-Z_], alphaDash: true
.
The value can only be [a-zA-Z0-9], alphaNumeric: true
.
The value can only be [a-zA-Z0-9_], alphaNumericDash: true
.
The value can only be ascii character, ascii: true
.
The encoding must be base64, base64: true
.
The byte length must be in a range, byteLength: options
.
module.exports = class extends think.Logic {
indexAction(){
let rules = {
field_name: {
byteLength: {min: 2, max: 4} // byte length is between 2 - 4
// byteLength: {min: 2} // minimum byte length is 2
// byteLength: {max: 4} // maximum byte length is 4
// byteLength: 10 // byte length need to be equal to 10
}
}
}
}
The value is a credit card numbers, creditCard: true
.
The value needs to be currency, currency: true | options
, options
refer to https://github.com/chriso/validator.js
.
The value needs to be date, date: true
.
Need to be decimal, for example: 0.1, .3, 1.1, 1.00003, 4.0, decimal: true
.
Need to be divisible by a number, divisibleBy: number
.
module.exports = class extends think.Logic {
indexAction(){
let rules = {
field_name: {
divisibleBy: 2 //can be divided by 2
}
}
}
}
Need to be email format, email: true | options
, options
refer to https://github.com/chriso/validator.js
.
Need to be a valid domain, fqdn: true | options
, options
refer to https://github.com/chriso/validator.js
.
Need to be a float number, float: true | options
, options
refer to https://github.com/chriso/validator.js
.
module.exports = class extends think.Logic {
indexAction(){
let rules = {
money: {
float: true, // float number
// float: {min: 1.0, max: 9.55} // Need to be a float,,with a minimum of 1.0 and maximum of 9.55
}
}
this.validate();
// todo
}
}
Need to include wide byte characters, fullWidth: true
.
Need to include nibble characters, halfWidth: true
.
Need to be a hexadecimal color value, hexColor: true
.
Need to be hexadecimal, hex: true
.
Need to be ip format, ip: true
.
Need to be ip4 format,ip4: true
.
Need to be ip6 format, ip6: true
.
Need for ISBN, isbn: true
.
Need to identify the code for the securities, isin: true
.
Need to iso8601 date format, iso8601: true
.
International Standard Serial Number, issn: true
.
Need to UUID (3,4,5 version), uuid: true
.
Need to be dataURI format, dataURI: true
.
Need to be md5,md5: true
.
Need to be mac address, macAddress: true
.
Need to include both nibble and full-byte characters, variableWidth: true
.
in some values, in: [...]
.
module.exports = class extends think.Logic {
indexAction(){
let rules = {
version: {
in: ['2.0', '3.0'] //need to be one of 2.0, 3.0
}
}
this.validate();
// todo
}
}
Can not be in some value, notIn: [...]
.
Need to be int, int: true | options
, options
refer to https://github.com/chriso/validator.js
.
module.exports = class extends think.Logic {
indexAction(){
let rules = {
field_name: {
int: true,
//int: {min: 10, max: 100} //need to be between 10 - 100
}
}
this.validate();
// todo
}
}
The length needs to be within a certain range, length: options
.
module.exports = class extends think.Logic {
indexAction(){
let rules = {
field_name: {
length: {min: 10}, //length can not be less than 10
// length: {max: 20}, //length can not be greater than 10
// length: {min: 10, max: 20}, //length needs to be between 10-20
// length: 10 //length needs to be equal to 10
}
}
this.validate();
// todo
}
}
Need to be lowercase letters, lowercase: true
.
Need to be capital letters, uppercase: true
.
Need to phone number, mobile: true | options
,options
refer to https://github.com/chriso/validator.js
.
module.exports = class extends think.Logic {
indexAction(){
let rules = {
mobile: {
mobile: 'zh-CN' //Must be China's cell phone number
}
}
this.validate();
// todo
}
}
Need to be ObjectID for MongoDB, mongoId: true
.
Need to include multi-byte characters, multibyte: true
.
Need to be url, url: true|options
,options
refer to https://github.com/chriso/validator.js
.
Need to be database query order, such as: name DESC, order: true
.
Need to be database query field, such as: name,title, field: true
.
let rules = {
file: {
required: true, // required defaults to false
image: true,
method: 'file' // File submitted through the post, verify the file needs to specify the method as `file`
}
}
Upload files need to be pictures, image: true
.
Need to start with some characters, startWith: string
.
Need to end with some characters, endWith: string
.
Need to be string, string: true
.
Array is required, array: true
, if the value of the field is an array, it will not be processed; split (,)
will be executed if the value of the field is a string; In other cases is converted to [field value]
.
Needs to be boolean type. 'yes'
, 'on'
, '1'
, 'true'
, true
is converted to true
.
Needs to be object type, object: true
.
The field should match the given regular expression.
module.exports = class extends think.Logic {
indexAction(){
this.rules = {
name: {
regexp: /thinkjs/g
}
}
this.validate();
// todo
}
}