The following content is translated by the machine
Use the @candyjs/cli command to create a project
$ # install the command
$ npm install -g @candyjs/cli
$
$ # run the command
$ candyjs-cli
The command above will be presented with prompts for several optional features such as project name and TypeScript.and so on.
After run the command, a simple application will be create
PROJECT_NAME
|
|- index.js
|
|- app
| |
| |-- controllers
| |
| |-- index
| | |
| | |-- IndexController.js
| |
| -- views
| |
| |-- index
| | |
| | |-- index.html
Go to the application directory to start the program
$ npm install && npm run start
Access
http://localhost:2333
PROJECT_NAME
|
|- index.js
|
|- node_modules
|
|- public
|
|- app
| |
| |-- controllers
| |
| |-- user
| | |
| | |-- IndexController.js
| | |-- OtherController.js
| |
| |-- goods
| | |
| | |-- IndexController.js
| | |-- OtherController.js
| |
| -- views
| |
| |-- user
| | |
| | |-- index.html
| | |-- other.html
| |
| |-- goods
| | |
| | |-- index.html
| | |-- other.html
| |
| -- runtime
|
index.js
The entry script is the first loop in the application startup process. There is only one entry script that contains the startup code that will listen to the client's connection
The entry script basically does the following
var CandyJs = require('candyjs');
var App = require('candyjs/web/Application');
new CandyJs(new App({
'id': 1,
'debug': true,
// application path
'appPath': __dirname + '/app',
// register modules
'modules': {
'bbs': 'app/modules/bbs'
},
// log setting
'log': {
'targets': {
'file': {
'classPath': 'candy/log/file/Log'
}
}
}
})).listen(2333, function(){
console.log('listen on 2333');
});
Applications are objects that govern the overall structure and lifecycle ofCandyJs
application systems.
CandyJs
has two kinds of Application:Web Application
and Rest Application
Various parameters can be introduced in the entry file. These parameters will eventually be assigned to the application instance object
candy/web/Application.id
This property is used to identify the only application
candy/web/Application.appPath
This property used to specified application directory
candy/web/Application.routesMap
Used to define the routing handler
'account': {
'classPath': 'app/controllers/user/IndexController',
'property': 'value'
}
candy/web/Application.modules
To register application module
// register a bbs module
'modules': {
'bbs': 'app/modules/bbs'
}
candy/web/Application.encoding
Project encoding
candy/web/Application.debug
Whether debugging is on
Other parameters passed in the entry file are passed to the application as a custom parameter
The controller is part of theMVC
schema that inherits the object ofcandy/core/AbstractController
class responsible for handling the request and generating the response
The controller consists ofactions
that is the most basic unit for executing the end user's request.
A controller has and only one entry action is calledrun
'use strict';
const Controller = require('candyjs/web/Controller');
class IndexController extends Controller {
// entry action
run(req, res) {
res.end('hello');
}
}
module.exports = IndexController;
ActionAspect is a special behavior class for filter user request.
ActionAspect
has two useful methods:beforeAction(actionEvent)
andafterAction(actionEvent)
In order to use aspects, you must first write the aspect class,which is the subclass ofcandyjs/core/ActionAspect
// app/filters/Cors.js
const ActionAspect = require('candyjs/core/ActionAspect');
module.exports = class Cors extends ActionAspect {
constructor() {
super();
this.cors = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS',
'Access-Control-Allow-Headers': '',
'Access-Control-Max-Age': 86400,
'Access-Control-Allow-Credentials': true
};
}
/**
* add http header
*/
beforeAction(actionEvent) {
let request = actionEvent.request;
let response = actionEvent.response;
let headers = this.generateHeaders(request);
for(let k in headers) {
response.setHeader(k, headers[k]);
}
if('OPTIONS' === request.method) {
actionEvent.valid = false;
response.writeHead(200, 'OK');
response.write('');
response.end();
}
}
generateHeaders(request) {
let ret = {};
// oirigin
if(undefined !== request.headers['origin'] && undefined !== this.cors['Access-Control-Allow-Origin']) {
if(this.cors['Access-Control-Allow-Origin'].indexOf(request.headers['origin']) >= 0) {
ret['Access-Control-Allow-Origin'] = request.headers['origin'];
}
// allow origin=* and allow credentials can not appear at the same time
if('*' === this.cors['Access-Control-Allow-Origin']) {
if(this.cors['Access-Control-Allow-Credentials']) {
throw new Error('Allowing credentials for wildcard origins is insecure');
} else {
ret['Access-Control-Allow-Origin'] = '*';
}
}
}
if(undefined !== request.headers['access-control-request-method']) {
ret['Access-Control-Allow-Methods'] = this.cors['Access-Control-Allow-Methods'];
}
if(undefined !== this.cors['Access-Control-Allow-Headers']) {
ret['Access-Control-Allow-Headers'] = this.cors['Access-Control-Allow-Headers'];
}
if(undefined !== this.cors['Access-Control-Allow-Credentials']) {
ret['Access-Control-Allow-Credentials'] = this.cors['Access-Control-Allow-Credentials'] ? 'true' : 'false';
}
if('OPTIONS' === request.method && undefined !== this.cors['Access-Control-Max-Age']) {
ret['Access-Control-Max-Age'] = this.cors['Access-Control-Max-Age'];
}
return ret;
}
}
It is simple to use your accept class, just add abehaviors()
method to your controller
class IndexController extends Controller {
// the value 'mycors' valus is an alias of your Aspect class, you can rename it to any
behaviors() {
return [
['mycors', 'app/filters/Cors']
];
}
run() {
// the behaviors() method will execute first
}
}
A filter is a class that implements the core/IFinter
interface,or any class withdoFilter (req, res, chain)
function can also considered to be a filter
The usage of filter and acpect are similar, except that the filter configuration is returned by thefilters()
function
class IndexController extends Controller {
// 'app/filters/MyFilter' is the class path of the filter
filters() {
return [
'app/filters/MyFilter'
];
}
run() {}
}
// app/filters/MyFilter
module.exports = class MyFilter {
doFilter(req, res, filterChain) {
// todo something
// manually execute the next filter
filterChain.doFilter(req, res);
}
}
Both aspect and filter can filter user requests before the controller executes, the filter is more user-friendly for asynchronous operations
In General, a router corresponds to a controller, there are two kinds of routers
route_prefix[/controllerId]
moduleId[/controllerId]
The part of[/controllerId]
can be omitted, and its default value isindex
, so the default controller isIndexControlelr
when visit http://localhost/
app/controllers/index/IndexControlelr
will be created, this is the default home page controller
when visit http://localhost/user
user
module will be searched first
if found, thenapp/modules/user/controllers/IndexController
will be created
if not found,thenapp/controllers/user/IndexController
will be created
when visit http://localhost/user/profile
user
module will be searched first
if found, thenapp/modules/user/controllers/ProfileController
will be created
if not, thenapp/controllers/user/ProfileController
will be created
when visit http://localhost/user/profile/settings
user
module will be searched first
if found thenapp/modules/user/controllers/SettingsController
will be created, the 'profile' part will be ignored
if not, thenapp/controllers/user/profile/SettingsController
will be created
Controller search order
module controller --> common controller
The model is part of theMVC
pattern that represents the object of the business data
视图是MVC
模式中的一部分 它用于给终端用户展示页面
CandyJs
提供了@candyjs/template-hbs
来负责模板渲染,查看详情
得益于CandyJs
灵活的架构设计,使得模板引擎使用非常便捷,这里有如下几种方式:
下面将使用Handlebars
对以上方式进行逐个讲解
// 全局配置方式是使用 candyjs 的别名系统实现的
// 这里的代码可以从源代码 examples 目录中找到
// 1. 在 app/libs 目录中建立一个模板引擎文件 CandyTemplate.js
const fs = require('fs');
const Handlebars = require('handlebars');
// 加载系统视图类
const View = require('candyjs/web/View');
class CandyTemplate extends View {
constructor(context) {
super(context);
this.handlebars = Handlebars.create();
}
// 模板引擎必须实现这个方法,因为它是渲染模板的入口
renderFile(file, parameters) {
fs.readFile(file, 'UTF-8', (err, template) => {
let compiled = this.handlebars.compile(template);
this.context.response.end( compiled(parameters) );
});
}
}
module.exports = CandyTemplate;
// 2. 经过第 1 步,我们的模板引擎就开发完了,是不是很简单
// 接下来在入口注册我们编写的模板引擎
const App = require('candyjs/web/Application');
const app = new App({
'id': 'template_test',
// 配置模板引擎
'defaultView': 'app/libs/CandyTemplate',
'appPath': __dirname + '/app'
});
new CandyJs(app).listen(2333, function(){
console.log('listen on 2333');
});
// 3. 准备模板 html
<html>
<body>
<ul>
{{#each list}}
<li><a href="/user?uid={{ id }}">{{ name }}</a></li>
{{/each}}
</ul>
</body>
</html>
// 4. 在控制器中使用模板引擎渲染页面
const Controller = require('candyjs/web/Controller');
const User = require('somepath/models/User');
class IndexController extends Controller {
run(req, res) {
this.fetchList(res);
}
async fetchList(res) {
const user = new User();
// 这里 data 是一个用户数组 [{id: xxx, name: xxx}]
let data = await user.getUserList();
// 可以获取到模板引擎实例
// 具体使用方式请参考 handlebars 模板引擎官方文档
// const handlebars = this.getView().handlebars;
// handlebars.todo
// 这里的 render 将使用我们制定的模板引擎渲染页面
this.render('index', {
list: data
});
}
}
// 1. 局部注入方式第 1 步也需要编写我们的模板引擎,参考全局配置方式
// 2. 在控制器中动态注入模板引擎
const Controller = require('candyjs/web/Controller');
const User = require('somepath/models/User');
const CandyTemplate = require('somepath/CandyTemplate');
class IndexController extends Controller {
run(req, res) {
this.fetchList(res);
}
async fetchList(res) {
const user = new User();
let data = await user.getUserList();
// 动态注入模板引擎
this.setView(new CandyTemplate(this.context));
this.render('index', {
list: data
});
}
}
module.exports = IndexController;
// 这种方式比较灵活,不需要编写模板引擎
const Controller = require('candyjs/web/Controller');
const Handlebars = require('handlebars');
const User = require('somepath/models/User');
class IndexController extends Controller {
run(req, res) {
this.fetchList(res);
}
async fetchList(res) {
const user = new User();
let data = await user.getUserList();
this.getView().getTemplateContent('index', (err, str) => {
// 直接使用模板引擎对内容进行编译并输出
let compiled = Handlebars.compile(str);
res.end( compiled({ list: data }) );
});
}
}
module.exports = IndexController;
If the user's controller inherits from thecandy/web/Controller
then can use thegetView()
method in the controller to get the view class instance
The view class provides the following API for user to use
findViewFile(view)
Used to get absolute path of a view filegetTemplateContent(view, callback)
Used to read the content of a view file
'use strict';
var Candy = require('candyjs/Candy');
var Controller = Candy.include('candy/web/Controller');
class IndexController extends Controller {
run(req, res) {
this.getView().getTemplateContent('index', (err, str) => {
res.end(str);
});
}
}
module.exports = IndexController;
Module is independent software unit.
It consists ofModel View Controller
and other necessary components
Note that unlike the common project directory, the controllers and views in the module do not have a subdirectory
In themodules
directory create a separate directory eg. bbs
modules
|
|-- bbs
| |
| |-- controllers
| | |
| | |-- IndexController.js
| |
| |-- views
| | |
| | |-- index.html
| |
| |-- other directory
The module created can not be recognized by the system, we need to manually register
var CandyJs = require('candyjs');
var App = require('candyjs/web/Application');
new CandyJs(new App({
...
// register module bbs
'modules': {
'bbs': 'app/modules/bbs'
},
...
})).listen(2333, function(){
console.log('listen on 2333');
});
Component is the base class ofBehavior and Event
Components are instances ofcandy/core/AbstractController
, or an extended class
Behavior classes are typically used in conjunction with component classes
Behavior is instances ofcandy/core/Behavior
, or an extended class
A behavior class can be used to enhance its functionality without changing the original component code
When a behavior is attached to a component, it will inject its methods and properties into the component and then access them as if they were to access the component's own methods and properties
The behavior class also can listen to the component's events and respond
CandyJs
implements an observer pattern
'use strict';
var Candy = require('candyjs/Candy');
var Controller = Candy.include('candy/web/Controller');
class IndexController extends Controller {
constructor(context) {
super(context);
this.on('myevent', function() {
console.log('myevent fired');
});
}
run(req, res) {
this.trigger('myevent');
res.end('hello');
}
}
module.exports = IndexController;
Create a class by inheritingcandy/core/Behavior
or its subclasses
'use strict';
var Candy = require('candyjs/Candy');
var Behavior = Candy.include('candy/core/Behavior');
// 行为类
class MyBehavior extends Behavior {
constructor() {
super();
}
// 监听控制器的 customEvent 事件
// 由于一个事件可以有多个处理程序 为保证顺序 这里必须使用数组
// 格式为 [行为名, 行为处理器]
events() {
return [
['customEvent', (e) => {
e.result = 'data processed by behavior';
}],
['customEvent2', (e) => {
e.result += '--process2';
}]
];
}
}
module.exports = MyBehavior;
The above code defines the behavior classMyBehavior
and provides two propertiesprop1 prop2
and a method for the component to attach the behaviormyFun()
You can attach a behavior to a component either statically or dynamically
To attach a behavior statically, override thebehaviors()
method of the component class to which the behavior is being attached
Thebehaviors()
method should return a list of behavior configurations
'use strict';
var Candy = require('candyjs/Candy');
var Controller = Candy.include('candy/web/Controller');
class IndexController extends Controller {
// 重写方法
behaviors() {
return [
['myBehavior', new MyBehavior()]
];
}
run(req, res) {
let data = {result: ''};
this.trigger('customEvent', data);
// 卸载行为
this.detachBehavior('myBehavior');
return data.result;
}
}
module.exports = IndexController;
To attach a behavior dynamically, call theattachBehavior()
method of the component to which the behavior is being attached
'use strict';
var Candy = require('candyjs/Candy');
var Controller = Candy.include('candy/web/Controller');
class IndexController extends Controller {
constructor(context) {
super(context);
// 动态附加行为 行为里面会监听 customEvent 事件
this.attachBehavior('myBehavior', new MyBehavior());
}
run(req, res) {
let data = {result: ''};
this.trigger('customEvent', data);
this.trigger('customEvent2', data);
this.detachBehavior('myBehavior');
return data.result;
}
}
module.exports = IndexController;
The middleware is the first part of the request to process the request and do the filtering and call the next middleware
CandyJs
temporarily only provides a middleware for handling static resources
CandyJs
defaults to non-processing of static resources that require the use of middleware
var CandyJs = require('candyjs');
var Candy = require('candyjs/Candy');
var App = require('candyjs/web/Application');
var Hook = require('candyjs/core/Hook');
var R = require('candyjs/midwares/Resource');
// serve the public dir as static resources
// before 4.20.0
// Hook.addHook(R.serve(__dirname + '/public'));
// after 4.20.0, add multiple dir support
Hook.addHook(new R(__dirname + '/public').serve());
new CandyJs(new App({
...
})).listen(2333, function(){
console.log('listen on 2333');
});
// in view file, we can use static resource like this: /public/js/lib.js
<script src="/js/lib.js"></script>
candy/web/URI and candy/web/URL
classes provide methods for uri and url operations
parseUrl()
Used for parse url
var URI = Candy.include('candy/web/URI');
var uri = new URI();
/*
{
source: 'http://xxx.com:8080/abc?q=1#anchor',
scheme: 'http',
user: undefined,
password: undefined,
host: 'xxx.com',
port: '8080',
path: '/abc',
query: 'q=1',
fragment: 'anchor'
}
*/
uri.parseUrl('http://xxx.com:8080/abc?q=1#anchor');
getReferer()
Used to get the referer urlgetHostInfo()
Used to get the host section of URIgetCurrent()
Used to get the current URLto(url[, params = null])
Used to create a url
var URL = Candy.include('candy/web/URL');
var url = new URL(req);
// return scheme://host/index/index
url.to('index/index');
// return scheme://host/index/index?id=1#anchor
url.to('index/index', {id: 1, '#': 'anchor'})
CandyJs
provides classescandy/http/Request
andcandy/http/Response
that handle requests and responses
Used to handle http requests
candy/http/Request
class provides a set of instances and static methods to manipulate the required data
getQueryString(param)
Instance method get the request parametergetParameter(param)
Instance method get the POST request parametergetCookie(name)
Instance method get cookiesgetHeaders()
Get http headers
In the use ofgetParameter()
to obtain the POST parameter temporarily need to rely on third-party analysis of the body of the middleware will otherwise back to the null
var Request = Candy.include('candy/http/Request');
var request = new Request(req);
var id = request.getQueryString('id');
...
Outputs a response message to the client
candy/http/Response
class provides a set of instances and static methods to manipulate the response data
setStatusCode(value[, text])
Set the http status codesetHeader(name, value)
Set the headersetContent(content)
Set the entity contentsetCookie(name, value[, options])
Set a cookiesend([content])
Send an HTTP response to the clientredirect(url[, statusCode = 302])
Page redirection
var Response = Candy.include('candy/http/Response');
var response = new Response(res);
response.setContent('some data from server');
response.send();
var Response = Candy.include('candy/http/Response');
var response = new Response(res);
response.redirect('http://foo.com');
The assistant class encapsulates some common operations
FileHelper
getDirname(dir)
normalizePath(path[, directorySeparator = '/'])
createDirectory(dir[, mode = 0o777[, callback = null]])
createDirectorySync(dir[, mode = 0o777])
StringHelper
nIndexOf(str, find, n)
Find the position where a string appears at the Nth occurrence in another stringtrimChar(str, character)
lTrimChar(str, character)
rTrimChar(str, character)
ucFirst(str)
htmlSpecialChars(str[, flag = 0[, doubleEncode = true]])
filterTags(str[, allowed = ''])
Filter html tagsTimeHelper
format(formats[, timestamp = Date.now()])
var Response = Candy.include('candy/helpers/FileHelper');
var Response = Candy.include('candy/helpers/StringHelper');
var Response = Candy.include('candy/helpers/TimeHelper');
// return /a/c
var path = FileHelper.normalizePath('/a/./b/../c');
// return <script>
var str = StringHelper.htmlSpecialChars('<script>');
// return abcxyz
var strTag = StringHelper.filterTags('<a>abc</a>xyz');
// return xxxx-xx-xx xx:xx:xx
var time = TimeHelper.format('y-m-d h:i:s');
CandyJs
provides a alias system
An alias is a string beginning with an@
sign. Each alias corresponds to a real physical path
InCandyJs
either create class or load class used alias
@candy
Points to the CandyJs directory@app
Project directory@runtime
Cache directory@root
Website root directoryUsers can customize aliases
// register alias
Candy.setPathAlias('@lib', '/home/www/library');
// create /home/www/library/MyClass class instance
var obj = Candy.createObject('lib/MyClass');
This function was experimental before 4.0.0
get(route, handler)
post(route, handler)
put(route, handler)
delete(route, handler)
patch(route, handler)
head(route, handler)
options(route, handler)
var CandyJs = require('candyjs');
var App = require('candyjs/rest/Application');
var app = new App({
id: 1,
appPath: __dirname + '/app',
debug: true
});
// simple get
app.get('/homepage', (req, res) => {
res.end('homepage');
});
// get with params
app.get('/posts/{id}', (req, res, params) => {
res.end(params.id);
});
// limit param type
app.get('/user/{id:\\d+}', (req, res, params) => {
res.end(params.id);
});
// process request with a class
// the code below means /xyz req will process with new Demo().index method
app.get('/xyz', 'app/api/Demo@index');
var candyJs = new CandyJs(app);
candyJs.listen('2333', () => {
console.log('listen on 2333')
});
The routes in RESTful are implemented using regular expressions. It can achieve very flexible routing configuration However, the routing performance is poor relative to MVC. ( Routing in the MVC pattern is not implemented in regular expressions. )
CandyJs
provides the ability to log processing but currently only supports file logs
Before using the log, you need to register in entry file
'log': {
'targets': {
'file': {
'classPath': 'candy/log/file/Log',
'logPath': __dirname + '/logs',
'logFile': 'system.log',
'maxFileSize': 10240 // 10 MB
}
},
'flushInterval': 10 // flush to disk every 10 times
}
error(message)
warning(message)
info(message)
trace(message)
flush()
var CandyJs = require('candyjs');
var log = CandyJs.getLogger();
log.error('This is a error message');
log.flush(); // flush data to disk
CandyJs
provides the ability to cache data processing but currently only supports file caching
Before using the cache, you need to register in entry file
'cache': {
'file': {
'classPath': 'candy/cache/file/Cache',
'cachePath': '...'
}
}
setSync(key, value, duration)
set(key, value, duration, callback)
getSync(key)
get(key, callback)
deleteSync(key)
delete(key, callback)
var Candy = require('candyjs/Candy');
var Cache = Candy.include('candy/cache/Cache');
var c = Cache.getCache('file');
// 同步
c.setSync('key', 'value');
var data = c.getSync('key');
// 异步
c.set('key2', 'value2', undefined, (err) => {
c.get('sync', (err, data) => {
res.end(data);
});
});
demo see here for detail
CandyJs
provide the ability to handle exception, you can implement thecandy/web/ExceptionHandler
class and override thehandlerException()
method to output custom message, or you can define a custom class with ahandlerException()
method
// index.js
const app = new App({
'id': 1,
'appPath': __dirname + '/app',
// exception handler class
'exceptionHandler': 'app/ExceptionHandler'
});
// app/ExceptionHandler.js
module.exports = class ExceptionHandler {
handlerException(exp, res) {
res.end('server error');
}
}
CandyJs
provides@candyjs/uploader
component to implement file upload. view detail
TypeScript is a strongly typed programming language
CandyJs
同样支持 typescript 项目以及使用 `tsc` 命令编译项目,但是需要安装@candyjs/tswrapper
模块
参考https://gitee.com/candyjs/candyjs-examples这里的 ts-* 或者 typescript 目录示例
或者参考https://github.com/candyframework/candyjs-examples这里的 ts-* 或者 typescript 目录示例