## 四层架构 ![四层架构](https://box.kancloud.cn/72d55b20fcc222986b6b68f1ba9c68c8_1024x468.png) ## 理解MVC * 一个**系统执行者**(`Actor`)可以执行多个**系统用例**(`Case`) * 但不能多个`Actor`执行同一个`Case` * 通常情况可以认为`Case`就是`Page` * 一个`Case`对应一个**视图控制器**(`ViewController`) * 也就是`MVC`中的`Controller`,其实属于四层架构中**交互层**的一部分 * 使用`index`方法提供基本`View` * 不是说那些个`HTML`文件就是`View`,它们仅仅是模板 * 经过浏览器解析渲染呈现出来的,`Actor`可视的部分才是`View` * 模板原本由`Smarty`等模板引擎控制,语法复杂,难以协作 * 正逐渐被`Vue`,`React`,`Angular`等前端框架替代,推荐`Vue` * 完成`Case`需要多个操作步骤(`Action`) * 注意:此时`URI`不变 * `Action`仅获取和过滤参数(防止`XSS`、`CSRF`、`SQL`注入等) * 原则上不对参数进行逻辑校验 * 除非因不同参数需要跳转不同`Controller` * 除非因不同参数需要调用不同`Logic` * 除非因不同参数可跳过不处理`Logic` * 参数透传给跨域的`Service`或者本域的`Logic`处理 * 判断处理结果,异常则中断过程直接返回;否则继续调用下一个`Service`或者`Logic` * 返回`HTML`、`Json`或者直接跳转(`Redirect`) * 一个`Controller`也许会调用多个`AppController` * 也就是`TP`框架中的`Service`,对应四层架构中**应用逻辑**或者**服务层** * 与`Controller`基本类似,但仅供内部跨域调用,用户不能直接访问 * 使用内部`HTTP`调用,防止服务中断后页面直接报错 * 不可以调用其它`Service`,以防死循环 * 一个`Service`可能会涉及到不同的领域逻辑(`Logic`) * 对应四层架构中**领域逻辑**或者**业务层** * 就近原则:谁使用参数,谁负责校验 * 不可拆分的最小业务单元 * `Logic`之间禁止相互调用,也不可以被跨域调用 * 一个`Logic`可能会调用不同的来源的数据模型(`Model`) * 对应四层架构中**数据模型**或者**事务层** * 领域模型 = 领域逻辑(`Logic`) + 数据模型(`Model`) * 重点是实体以及实体之间的联系,也就是**ER图** * 负责实现状态机以及状态码的中文解释等 * 通俗来说,以餐馆为例 * 店小二是`Controller`,负责将顾客点菜,并将菜单发给厨师,把菜端给客户; * 厨师是`Logic`,只有他才知道原料是否有货,才能把菜做好给小二;小二不能直接拿到原料就对顾客说,只有这些东西,你自己做来吃吧;顾客要的始终是菜; * 后勤是`Model`,负责原料的采购; * 假设这个餐馆是肯德基或者麦当劳,那么就需要一个中央厨房,将菜品标准化,这个中央厨房就是`Service` ## 目录规划 * **站点目录**是指存放网站所有相关文件的目录 * **站点根目录**是指用户通过域名能够访问到的部分,通常是站点目录的子目录 * 如果是IIS服务器,一般为:`wwwroot` * 如果是`Apache`服务器,一般为:`htdocs` * 而在`ThinkPHP`框架中,一般为:`public` * `Home`原本是指站点首页,对应的是**根目录**下的`index`文件 * `index`文件有很多,可能每个目录下面都有一个,但只有**根目录**下的这个才被称为`Home` * 但在框架中,`index.php`已经变成前端控制器 * 所以只能引入`Home`模块作为默认模块 * 每个模块都有一个默认的方法`index`,提供默认`View` * `Application`:按照产品来划分目录 * 通用产品 * `base`:放置所有基类 * `controller`:按照域名前缀划分子目录 * 没有任何业务逻辑和服务 * `view`:同样按照域名前缀来划分,但不要直接修改,请参考**前端工程**里的说明 * `view/common`:存放全站公共的基础函数 * `home`:首页 * 包括通用的业务逻辑和服务 * `profile`:用户中心 * `business`:商户中心 * 配置文件 * `.env`:**站点目录**下的隐藏文件,配置不同开发环境的数据库连接参数等 * `route_service.php`:提供内部`Service`调用 * `common.php`:尽量不要使用,所有助手函数分类放到`Helper`里面去 * `Result.php`:`Logic`和`Service`的统一返回值 ``` . ├── base │   ├── controller │   │   ├── api │   │   ├── m │   │   ├── www │   │   └── xdb │   ├── logic │   │   ├── BaseLogic.php │   │   └── Result.php │   ├── model │   │   └── BaseModel.php │   ├── service │   │   ├── BaseService.php │   │   └── ServiceResult.php │   └── view │   └── xdb ├── business │   ├── config.php │   ├── controller │   ├── logic │   ├── model │   ├── service │   └── view ├── command.php ├── common.php ├── config.php ├── database.php ├── home │   ├── controller │   ├── logic │   ├── model │   ├── service │   └── view ├── profile │   ├── controller │   ├── logic │   ├── model │   └── service ├── route_service.php ├── yan │   ├── config.php │   ├── controller │   ├── database.php │   ├── logic │   ├── model │   └── view └── zhuan ``` ## 前端工程 ### 访问目录: > 部署在`/data/htdocs/**` * CDN:`static.tugou.com` * 内网: `/public/static` ### 源码目录: > 部署在`**/view/[site]` * 工程目录:`@asset` * 此目录下所有文件最终生成到同级目录下的`@static` * `.css`文件统一建议一个scss目录存放 * 发布目录:`@static` * 此目录下的所有文件必须上传`SVN`,并发布到对应的访问目录 ### 编译发布: * 安装最新`LTS`版本的`NodeJs`和`NPM` * 进入站点目录执行:`npm install` * 启动文件监听:`npm start` ~~~ { "name": "tugou-static", "version": "1.0.2", "description": "this is tugou enterprise project", "main": "package.json", "dependencies": { "gulp": "3.9.1", "gulp-autoprefixer": "^3.1.1", "gulp-clean-css": "^2.3.2", "gulp-debug": "^3.1.0", "gulp-rem": "^1.1.6", "gulp-ruby-sass": "^2.1.1", "gulp-sftp": "^0.1.5" }, "devDependencies": {}, "scripts": { "start": "gulp" }, "author": "ronesam", "private": "true", "license": "ISC" } ~~~ ### 第三方: > 部署在`/vendor` * 使用`Bower`下载模块到`@asset` * 拷贝`@asset/module/dist`目录内的文件到`@static/moudule-v1` * 发布到`static.tugou.com/vendor` ~~~ # bower.json { "name": "tugou-static-vendor", "description": "兔狗第三方库", "main": "package.json", "authors": [ "ronesam" ], "license": "MIT", "homepage": "", "private": true, "ignore": [ "**/.*", "node_modules", "bower_components", "test", "tests" ], "dependencies": { "blueimp-md5-v2": "blueimp-md5#^2.7.0", "bootstrap-v3": "bootstrap#^3.3.7", "clipboard-v1": "clipboard#^1.6.1", "devbridge-autocomplete-v1": "devbridge-autocomplete#^1.3.0", "element-ui-v1": "element-ui#^1.2.4", "es6-promise-v4": "es6-promise#^4.0.5", "flatpickr-v2": "flatpickr-calendar#^2.4.4", "font-awesome-v4": "font-awesome#^4.7.0", "jBox-v0": "jbox#^0.4.7", "jquery-cookie-v1": "jquery-cookie#^1.4.1", "jquery-dateFormat-v1": "jquery-dateFormat#^1.0.0", "jquery-nicescroll-v3": "jquery-nicescroll#^3.6.8", "jquery-powertip-v1": "jquery-powertip#^1.3.0", "jquery-v1": "jquery#^1.12.4", "jquery-v2": "jquery#^2.2.4", "jquery-v3": "jquery#^3.1.1", "js-cookie-v2": "js-cookie#^2.1.3", "lodash-v4": "lodash#^4.17.4", "moment-v2": "moment#^2.17.1", "swiper-v3": "swiper#^3.4.1", "velocity-v1": "velocity#^1.4.3", "vue-resource-v1": "vue-resource#^1.2.1", "vue-v1": "vue#^1.0.28", "vue-v2": "vue#^2.2.2", "vue-validator-v2": "vue-validator#^2.1.6" } } ~~~