[转] 糙男手写权限管理系统 laravel ACL

没有想像的复杂。

http://heera.it/laravel-5-0-acl-using-middleware#.Vqtg7bB96SM

 

Well, actually building an ACL (Access Control Layer) in Laravel is really very easy than using a third party package and I always prefer my own implementation. In Laravel - 4x we’ve used Route Filter mechanism to build an ACL but in Laravel - 5.0 now we have Middleware instead of Filter and it’s much better, IMO.

The idea behind the ACL is that, we want to protect our routes using user roles and permissions and in this case the underlying mechanism is quite same in both versions but only difference is that, in Laravel - 5.0 theMiddleware is the layer (for filtering) between our routes and the application, while in Laravel - 4x the Route Filter was used for filtering the requests before the user gets into the application. In this article, I’ll show how it’s easily possible to implement an ACL from the scratch using Middleware.

It’s possible to implement ACL in different ways but this is what I’ve used in Laravel - 4x and also in Laravel - 5.0 and this could be enhanced or improved but approach may varies but the idea is same, filtering user requests before entering into the application layer.
TO IMPLEMENT THIS, WE NEED 4 TABLES:
  • 1. users
  • 2. roles
  • 3. permissions
  • 4. permission_role (Pivot Table)
DATABASE MIGRATION FOR users TABLE:
01 <?php
02
03 use Illuminate\Database\Migrations\Migration;
04 use Illuminate\Database\Schema\Blueprint;
05
06 class CreateUsersTable extends Migration {
07
08     /**
09      * Run the migrations.
10      *
11      * @return void
12      */
13     public function up()
14     {
15         Schema::create('users', function(Blueprint $table)
16         {
17             $table->increments('id')->unsigned();
18             $table->integer('role_id')->unsigned();
19             $table->string('email')->unique();
20             $table->string('password');
21             $table->string('first_name');
22             $table->string('last_name');
23             $table->rememberToken();
24             $table->timestamps();
25             $table->softDeletes();
26         });
27     }
28
29     /**
30      * Reverse the migrations.
31      *
32      * @return void
33      */
34     public function down()
35     {
36         Schema::drop('users');
37     }
38 }
DATABASE MIGRATION FOR roles TABLE:
01 <?php
02
03 use Illuminate\Database\Schema\Blueprint;
04 use Illuminate\Database\Migrations\Migration;
05
06 class CreateRolesTable extends Migration {
07
08    /**
09     * Run the migrations.
10     *
11     * @return void
12     */
13    public function up()
14    {
15       Schema::create('roles', function(Blueprint $table)
16       {
17          $table->increments('id');
18          $table->string('role_title');
19          $table->string('role_slug');
20       });
21    }
22
23    /**
24     * Reverse the migrations.
25     *
26     * @return void
27     */
28    public function down()
29    {
30       Schema::drop('roles');
31    }
32
33 }
DATABASE MIGRATION FOR permissions TABLE:
01 <?php
02
03 use Illuminate\Database\Schema\Blueprint;
04 use Illuminate\Database\Migrations\Migration;
05
06 class CreatePermissionsTable extends Migration {
07
08    /**
09     * Run the migrations.
10     *
11     * @return void
12     */
13    public function up()
14    {
15       Schema::create('permissions', function(Blueprint $table)
16       {
17          $table->increments('id');
18          $table->string('permission_title');
19          $table->string('permission_slug');
20          $table->string('permission_description')->nullable();
21       });
22    }
23
24    /**
25     * Reverse the migrations.
26     *
27     * @return void
28     */
29    public function down()
30    {
31       Schema::drop('permissions');
32    }
33
34 }
DATABASE MIGRATION FOR permission_role TABLE:
01 <?php
02
03 use Illuminate\Database\Schema\Blueprint;
04 use Illuminate\Database\Migrations\Migration;
05
06 class CreatePermissionRoleTable extends Migration {
07
08    /**
09     * Run the migrations.
10     *
11     * @return void
12     */
13    public function up()
14    {
15       Schema::create('permission_role', function(Blueprint $table)
16       {
17          $table->increments('id');
18          $table->integer('permission_id');
19          $table->integer('role_id');
20       });
21    }
22
23    /**
24     * Reverse the migrations.
25     *
26     * @return void
27     */
28    public function down()
29    {
30       Schema::drop('permission_role');
31    }
32
33 }

These tables are required to build the ACL fields could be changed (add/remove) but to build the relationship between tables we need foreign keys and we can’t remove those fields such as role_id in users table and the pivot table is also necessary as it is.

Now, we need to create the middleware class to check the user permissions and we can create it using php artisan make:middleware CheckPermission from command line/terminal. This will create a skeleton of a middlewareclass in app/Http/Middleware directory as CheckPermission.php and now we need to edit that class as given below:

01 <?php namespace App\Http\Middleware;
02
03 use Closure;
04 use Illuminate\Contracts\Routing\Middleware;
05
06 class CheckPermission implements Middleware {
07
08    /**
09     * Handle an incoming request.
10     *
11     * @param  \Illuminate\Http\Request  $request
12     * @param  \Closure  $next
13     * @return mixed
14     */
15    public function handle($request, Closure $next)
16    {
17       if($this->userHasAccessTo($request)) {
18
19          view()->share('currentUser', $request->user());
20          
21          return $next($request);
22       }
23
24       return redirect()->route('home');
25    }
26
27    /*
28    |--------------------------------------------------------------------------
29    | Additional helper methods for the handle method
30    |--------------------------------------------------------------------------
31    */
32
33    /**
34     * Checks if user has access to this requested route
35     *
36     * @param  \Illuminate\Http\Request  $request
37     * @return Boolean true if has permission otherwise false
38     */
39    protected function userHasAccessTo($request)
40    {
41       return $this->hasPermission($request);
42    }
43
44    /**
45     * hasPermission Check if user has requested route permimssion
46     *
47     * @param  \Illuminate\Http\Request $request
48     * @return Boolean true if has permission otherwise false
49     */
50    protected function hasPermission($request)
51    {
52       $required = $this->requiredPermission($request);
53
54       return !$this->forbiddenRoute($request) && $request->user()->can($required);
55    }
56
57    /**
58     * Extract required permission from requested route
59     *
60     * @param  \Illuminate\Http\Request  $request
61     * @return String permission_slug connected to the Route
62     */
63    protected function requiredPermission($request)
64    {
65       $action = $request->route()->getAction();
66
67       return isset($action['permission']) ? explode('|', $action['permission']) : null;
68    }
69
70    /**
71     * Check if current route is hidden to current user role
72     *
73     * @param  \Illuminate\Http\Request $request
74     * @return Boolean true/false
75     */
76    protected function forbiddenRoute($request)
77    {
78       $action = $request->route()->getAction();
79
80       if(isset($action['except'])) {
81          
82          return $action['except'] == $request->user()->role->role_slug;
83       }
84
85       return false;
86    }
87 }

Now, we need to create other classes (Eloquent Model) in app/DB directory. Here, in Laravel - 5.0 the models directory is not available and by default the app directory contains the Eloquent model classes such as Userbut I’ve created the DB directory to house all of my Eloquent/Fluent classes but it’s not mandatory. Anyways, let’s create those classes (User, Role and Permission) now in app/DB or just in app (The full path of User class must be given in the config/Auth.php file).

01 <?php namespace App\DB\User;
02
03 use Illuminate\Auth\Authenticatable;
04 use Illuminate\Database\Eloquent\Model;
05 use Illuminate\Auth\Passwords\CanResetPassword;
06 use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
07 use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
08 use App\Http\Requests\Auth\RegisterRequest;
09 use App\DB\User\Traits\UserACL;
10 use App\DB\User\Traits\UserAccessors;
11 use App\DB\User\Traits\UserQueryScopes;
12 use App\DB\User\Traits\UserRelationShips;
13
14 class User extends Model implements AuthenticatableContract, CanResetPasswordContract {
15
16    use Authenticatable, CanResetPassword;
17
18    /**
19     * Application's Traits (Separation of various types of methods)
20     */
21    use UserACL, UserRelationShips;
22 }

The traits are used to separate the code in User class to keep the code clean and easily maintainable, (app/DB/User/Traits/UserACL.php trait):

001 <?php namespace App\DB\User\Traits;
002
003 trait UserACL {
004    
005    /**
006     * can Checks a Permission
007     *
008     * @param  String $perm Name of a permission
009     * @return Boolean true if has permission, otherwise false
010     */
011    public function can($perm = null)
012    {
013       if($perm) {
014
015          return $this->checkPermission($this->getArray($perm));  
016       }
017
018       return false;
019    }
020
021    /**
022     * Make string to array if already not
023     *
024     * @param  Mixed $perm String/Array
025     * @return Array
026     */
027    protected function getArray($perm)
028    {
029       return is_array($perm) ? $perm : explode('|', $perm);
030    }
031
032    /**
033     * Check if the permission matches with any permission user has
034     *
035     * @param  Array $perm Name of a permission (one or more separated with |)
036     * @return Boolean true if permission exists, otherwise false
037     */
038    protected function checkPermission(Array $permArray = [])
039    {
040       $perms = $this->role->permissions->fetch('permission_slug');
041       
042       $perms = array_map('strtolower', $perms->toArray());
043
044       return count(array_intersect($perms, $permArray));
045    }
046
047    /**
048     * hasPermission Checks if has a Permission (Same as 'can')
049     *
050     * @param  String $perm [Name of a permission
051     * @return Boolean true if has permission, otherwise false
052     */
053    public function hasPermission($perm = null)
054    {
055       return $this->can($perm);
056    }
057
058    /**
059     * Checks if has a role
060     *
061     * @param  String $perm [Name of a permission
062     * @return Boolean true if has permission, otherwise false
063     */
064    public function hasRole($role = null)
065    {
066       if(is_null($role)) return false;
067       
068       return strtolower($this->role->role_slug) == strtolower($role);
069    }
070
071    /**
072     * Check if user has given role
073     *
074     * @param  String $role role_slug
075     * @return Boolean TRUE or FALSE
076     */
077    public function is($role)
078    {
079       return $this->role->role_slug == $role;
080    }
081
082    /**
083     * Check if user has permission to a route
084     *
085     * @param  String $routeName
086     * @return Boolean true/false
087     */
088    public function hasRoute($routeName)
089    {
090       $route = app('router')->getRoutes()->getByName($routeName);
091
092       if($route) {
093
094          $action = $route->getAction();
095          
096          if(isset($action['permission'])) {
097             
098             $array = explode('|', $action['permission']);
099
100             return $this->checkPermission($array);
101          }
102       }
103
104       return false;
105    }
106
107    /**
108     * Check if a top level menu is visible to user
109     *
110     * @param  String $perm
111     * @return Boolean true/false
112     */
113    public function canSeeMenuItem($perm)
114    {
115       return $this->can($perm) || $this->hasAnylike($perm);
116    }
117    
118    /**
119     * Checks if user has any permission in this group
120     *
121     * @param  String $perm Required Permission
122     * @param  Array $perms User's Permissions
123     * @return Boolean true/false
124     */
125    protected function hasAnylike($perm)
126    {
127       $parts = explode('_', $perm);
128       
129       $requiredPerm = array_pop($parts);
130
131       $perms = $this->role->permissions->fetch('permission_slug');
132
133       foreach ($perms as $perm)
134       {
135          if(ends_with($perm, $requiredPerm)) return true;
136       }
137
138       return false;
139    }
140 }

The app/DB/User/Traits/UserRelationShips.php trait (for relationship methods):

01 <?php namespace App\DB\User\Traits;
02
03 trait UserRelationShips {
04
05    /**
06     * role() one-to-one relationship method
07     *
08     * @return QueryBuilder
09     */
10    public function role()
11    {
12       return $this->belongsTo('App\DB\Role');
13    }
14 }

The Role class (app/DB/Role.php):

01 <?php namespace App\DB;
02
03 use Illuminate\Database\Eloquent\Model;
04
05 class Role extends Model {
06
07     /**
08     * users() one-to-many relationship method
09     *
10     * @return QueryBuilder
11     */
12    public function users()
13    {
14       return $this->hasMany('App\DB\User\User');
15    }
16
17    /**
18     * permissions() many-to-many relationship method
19     *
20     * @return QueryBuilder
21     */
22    public function permissions()
23    {
24       return $this->belongsToMany('App\DB\Permission');
25    }
26 }

The Permission class (app/DB/Permission.php):

01 <?php namespace App\DB;
02
03 use Illuminate\Database\Eloquent\Model;
04
05 class Permission extends Model {
06
07    /**
08     * roles() many-to-many relationship method
09     *
10     * @return QueryBuilder
11     */
12    public function roles()
13    {
14       return $this->belongsToMany('App\DB\Role');
15    }
16 }

Now, before we can use our Middleware in any route declaration, we need to add it it in the app/Http/Kernel.phpfile and by default, there are already other middlewares added in that file by Laravel in the $routeMiddlewarearray and it looks like this:

01 /**
02  * The application's route middleware.
03  *
04  * @var array
05  */
06 protected $routeMiddleware = [
07     'auth' => 'App\Http\Middleware\Authenticate',
08     'auth.basic' => 'Illuminate\Auth\Middleware\AuthenticateWithBasicAuth',
09     'guest' => 'App\Http\Middleware\RedirectIfAuthenticated',
10 ];

We’ll just add our middleware at the end of this array like this:

1 'acl' => 'App\Http\Middleware\CheckPermission',

The acl is the alias which we’ll use in our routes when declaring the routes for limited access, for example take a look at this app/Http/routes.php file:

01 // Home Page URI (not protected)
02 $router->get('/', ['uses' => 'HomeController@index', 'as' => 'home']);
03
04 // Protected Routes by auth and acl middleware
05 $router->group(['prefix' => 'admin', 'namespace' => 'Admin', 'middleware' => ['auth', 'acl']],function() use ($router)
06 {
07    $router->get('dashboard', [
08       'uses' => 'DashboardController@index',
09       'as' => 'dashboard',
10       'permission' => 'manage_own_dashboard',
11       'menuItem' => ['icon' => 'fa fa-dashboard', 'title' => 'Dashboard']
12    ]);
13
14     // Group: Users
15     $router->group(['prefix' => 'users', 'namespace' => 'User'], function() use ($router)
16     {
17         $router->get('/{role?}', [
18             'uses' => 'UserController@index',
19             'as' => 'admin.users',
20             'permission' => 'view_user',
21             'menuItem' => ['icon' => 'clip-users', 'title' => 'Manage Users']
22         ])->where('role', '[a-zA-Z]+');
23
24         $router->get('view/{id}', [
25             'uses' => 'UserController@viewUserProfile',
26             'as' => 'admin.user.view',
27             'permission' => 'view_user'
28         ]);
29     });
30 });

In this file, all of our routes are protected and requires the user to stay logged in (auth is used to check whether the user is logged in or not, available by default in Laravel) and the acl will check if the user has a given permission or not, for example, the dashboard url/route requires the permission manage_own_dashboard because it has'permission' => 'manage_own_dashboard' and in our middleware we’ll check if the route has the key permission in it’s action and if the value of permission key (which is a permission) is available in the currently logged in users role permissions list then we’ll allow the user to access the application, otherwise we’ll disallow the user access.

On the time, when this article is being written, the Laravel-5.0 framework isn’t released and after the final release, things may change so please make sure you check the updates on Laravel websiteand hoping, in the first week of January’15, Laravel-5.0 will be released.
HOW IT WORKS?

Actually, this is just a demonstration of only the ACL but I didn’t provide any code which provides an user interface that let’s the admin a way to create user roles by attaching the permissions but only the core idea to implement the ACL in the application.

So, a brief idea is that, each user will be assigned a role when user account is created from the front-end or by the admin (a default role could be set from the back-end) and the roles will be created by admin (super user) from the back-end and permissions will be attached to each roles. So, a role for example Moderator which could have a permission as Suspend User (permission title) and suspend_user (permission_slug) so we can attachsuspend_user in a Route using something like this:

1 $router->get('user/suspend/{id}', [
2     uses' => 'UserController@closeUserAccount',
3     'as' => 'admin.user.suspend',
4     'permission' => 'suspend_user',
5     'middleware' => ['acl']
6 ]);

So, this route requires the permission suspend_user and in the acl middleware we can check the user’s permission and can protect the route if the user doesn’t has that permission. That’s it.

In this article, I tried discussed how to organize the tables and classes and how to filter the requests using middleware and what classes could be required but not full implementation of a fully functional system, it just gives an abstract idea to create an ACL functionality using Laravel - 5 Middleware from the scratch (without using any third party package).

Since the Laravel-5.0 is still under development, so I didn’t provide the fully functional code here and once the framework is released, then I’ll write another post, maybe in two parts as a series with fully functional code for building an ACL from the scratch, but for now, that’s all. Thank you.

UPDATE:

In my code example, I’ve used menuItem as a key in route declaration. This key is used to build a dynamic menu and to build the menu I’ve created a class/library which is located at app/Libs/Navigation folder of my project. The menu builder class is given below:

001 <?php namespace App\Libs\Navigation;
002
003 use Illuminate\Support\Arr;
004 use Illuminate\Http\Request;
005 use Illuminate\Routing\Router;
006
007 class Builder {
008
009    private $menuItems = [];
010    private $routes = NULL;
011    private $user = NULL;
012
013    public function __construct(Arr $arr, Request $request, Router $router)
014    {
015       $this->arr = $arr;
016       $this->request = $request;
017       $this->user = $request->user();
018       $this->router = $router;
019       $this->routes = $router->getRoutes();
020    }
021    
022    /**
023     * Extract all top level menu items from routes
024     *
025     * @return Array A multi-dementional array
026     */
027    public function build()
028    {
029       foreach ($this->routes->getIterator() as $it) {
030
031          $action = $it->getAction();
032          
033          if(!$this->forbidden($action) && $item = $this->arr->get($action, 'menuItem')) {
034          
035             $routeName = $this->arr->get($action, 'as');
036
037             $this->menuItems[$routeName] = array('icon' => @$item['icon'], 'title' => @$item['title']);
038          }
039       }
040
041       return $this;
042    }
043
044    /**
045     * Check if current route is hidden to current user role
046     *
047     * @param  \Illuminate\Http\Request $request
048     * @return Boolean true/false
049     */
050    protected function forbidden($action)
051    {
052       if(isset($action['except']))
053       {
054          return $action['except'] == $this->user->roles->fetch('role_slug');
055       }
056
057       return false;
058    }
059
060    /**
061     * Render all HTML li tags
062     *
063     * @param  Array $menuItems
064     * @param  string $itemView View name to generate a single li
065     * @return HTML li items as String
066     */
067    public function render($itemView = 'admin.layouts.partials.content.navLiTemplate')
068    {
069       $listElements = [];
070
071       foreach ($this->menuItems as $routeName => $itemArray) {
072          
073          $listElements[] = $this->getListItem($routeName, $itemArray);
074       }
075
076       return join($listElements, '');
077    }
078
079    /**
080     * Build a menu item by checking the permissions.
081     *
082     * @param  String $routeName Name of the route to generate link
083     * @param  Array $itemArray List of route's menuItem array
084     * @return HTML li Element as menu item
085     */
086    protected function getListItem($routeName, $itemArray)
087    {
088       $action = $this->routes->getByName($routeName)->getAction();
089
090       $permission = $this->arr->get($action, 'permission');
091
092       if((!empty($permission) && $this->user->canSeeMenuItem($permission)) ||empty($permission))
093       {
094
095          $except = $this->arr->get($action, 'except');
096
097          if((!isset($except) || (isset($except) && !count($except))) || (isset($except) && !$this->user->is($except))) {
098             
099             $data = [
100                'link' => route($routeName),
101                'active' => $this->isActive($routeName),
102                'title' => $this->getTitle($itemArray['title']),
103                'icon' => $itemArray['icon'],
104                'id' => str_replace(' ', '-', strtolower($itemArray['title']))
105             ];
106             
107             return view('admin.layouts.partials.content.navLiTemplate', $data);
108          }
109       }
110    }
111
112    /**
113     * Mark active menu item
114     *
115     * @param  String $routeName Route Name
116     * @return HTML Attribute or NULL
117     */
118    protected function isActive($routeName)
119    {
120       return $this->router->getCurrentRoute()->getName() == $routeName ? 'active' : NULL;
121    }
122 }

Further, to invoke the menu builder class I’ve created a helper function in a helper file which isapp/Helpers/fucntions.php and the function is given below:

1 /**
2  * Build the top level navigation
3  *
4  * @return HTML list items (li) as string
5  */
6 function renderMenu()
7 {
8    return app('App\Libs\Navigation\Builder')->build()->render();
9 }

Finally, I’ve called the function from my view where I wanted to show the menu and the view is a partial of my admin layout which contains following code:

1 <!-- admin/layouts/partials/nav.blade.php-->
2 <!-- start: MAIN NAVIGATION MENU -->
3 <ul class="main-navigation-menu">
4    {!! renderMenu() !!}
5 </ul>
6 <!-- end: MAIN NAVIGATION MENU -->

Stay in touch, full (improved code) will be uploaded on github later.

 

780 total views, 1 views today

[转]jQuery常用插件整理

from:

https://github.com/qloog/laravel5-backend/wiki/jQuery%E5%B8%B8%E7%94%A8%E6%8F%92%E4%BB%B6%E6%95%B4%E7%90%86

jQuery常用插件整理
qloog edited this page on 25 Dec 2015 · 29 revisions
Pages 3
Home
jQuery常用插件整理
前端组件安装整理
Clone this wiki locally

https://github.com/qloog/laravel5-backend.wiki.git
Clone in Desktop
常用插件

日期插件

github: https://github.com/eternicode/bootstrap-datepicker
demo: http://eternicode.github.io/bootstrap-datepicker/?markup=input&format=&weekStart=&startDate=&endDate=&startView=0&minViewMode=0&todayBtn=false&clearBtn=false&language=en&orientation=auto&multidate=&multidateSeparator=&keyboardNavigation=on&forceParse=on#sandbox

下拉选择插件

github: https://github.com/harvesthq/chosen
demo: http://harvesthq.github.io/chosen/

步骤插件

github: https://github.com/rstaib/jquery-steps
demo: http://www.jquery-steps.com/Examples

富文本编辑器

github: https://github.com/mycolorway/simditor
demo: http://simditor.tower.im/

图片裁剪

demo: http://www.fullavatareditor.com/demo.html

表格插件jqGrid

blog: http://www.trirand.com/blog/?page_id=5
demo: http://www.guriddo.net/demo/bootstrap/
more: http://www.guriddo.net/demo/demos/jqgrid/
PS:还有一个是datatable

css动画

demo: http://daneden.github.io/animate.css/

单页导航

demo: http://github.com/davist11/jQuery-One-Page-Nav

ajax上传插件

github: https://github.com/blueimp/jQuery-File-Upload
demo: http://blueimp.github.io/jQuery-File-Upload/
配置项说明:https://github.com/blueimp/jQuery-File-Upload/wiki/Options
最小化配置说明:https://github.com/blueimp/jQuery-File-Upload/wiki/Basic-plugin
PS: wiki很强大,但依赖的js较多(jquery.fileupload-*.js, 有5个+)

ajaxFileUpload(不好使,存在bug)

github: https://github.com/davgothic/AjaxFileUpload
使用说明:http://www.cnblogs.com/kissdodog/archive/2012/12/15/2819025.html
PS: 只需要引入一个js文件即可。

Web Uploader

github: https://github.com/fex-team/webuploader/
官网:http://fex.baidu.com/webuploader/
PS: WebUploader是由Baidu WebFE(FEX)团队开发的一个简单的以HTML5为主,FLASH为辅的现代文件上传组件。在现代的浏览器里面能充分发挥HTML5的优势,同时又不摒弃主流IE浏览器,沿用原来的FLASH运行时,兼容IE6+,iOS 6+, android 4+。两套运行时,同样的调用方式,可供用户任意选用。 采用大文件分片并发上传,极大的提高了文件上传效率。

select2

github: https://github.com/select2/select2
官网: https://select2.github.io/
PS: Select2 gives you a customizable select box with support for searching, tagging, remote data sets, infinite scrolling, and many other highly used options.

zoomooz

demo: http://jaukia.github.io/zoomooz/
PS: 主要是做一些酷炫的动画效果。

fancybox

demo: http://fancyapps.com/fancybox/
PS: 弹层展示图片,支持左右点击切换、底部缩略图、顶部按钮、视频、地图等

jQuery Zoom

demo: http://www.jacklmoore.com/zoom/
PS: A plugin to enlarge images on touch, click, or mouseover.

jquery.imagezoom.js

demo: http://www.ijquery.cn/?p=489 PS: 鼠标悬停图片放大镜特效

ZOOM – jQuery photo gallery plugin

demo: http://www.xwcms.net/webAnnexImages/fileAnnex/20140406/55886/index.html
PS: jQuery制作zoom图片全屏放大弹出层插件。

表格插件:Bootstrap Table

github: https://github.com/wenzhixin/bootstrap-table-examples
demo: http://issues.wenzhixin.net.cn/bootstrap-table/
官网:http://bootstrap-table.wenzhixin.net.cn/

经典的网页对话框组件

github: https://github.com/aui/artDialog
demo: http://aui.github.io/artDialog/

表单校验插件 validate

github: https://github.com/jzaefferer/jquery-validation
文档: http://jqueryvalidation.org/validate
demo:http://jqueryvalidation.org/files/demo/
PS:
jquery.validate.min.js 主文件
additional-methods.min.js 额外添加的一些验证方法

Form 插件

官网: http://jquery.malsup.com/form/
使用案例: http://www.cnblogs.com/heyuquan/p/form-plug-async-submit.html

notifications 库 toastr

Github: https://github.com/CodeSeven/toastr
DEMO: http://codeseven.github.io/toastr/demo.html

Layer 一个可以让你想到即可做到的javascript弹窗(层)解决方案

官网:http://layer.layui.com/

React插件

react-bootstrap (基于 react 0.14.x)

Github: https://github.com/react-bootstrap/react-bootstrap
demo: http://react-bootstrap.github.io/

react-bootstrap-table

Github: https://github.com/AllenFang/react-bootstrap-table
DEMO: http://allenfang.github.io/react-bootstrap-table/example.html

fixed-data-table

Github: https://github.com/facebook/fixed-data-table
DEMO: http://facebook.github.io/fixed-data-table/
Status API Training Shop Blog About Pricing

868 total views, no views today

[转]我比较喜欢的一个相册设计

https://blueimp.github.io/Bootstrap-Image-Gallery/

907 total views, no views today

[转]最近的常用的推荐算法

今天被拉着开了一天的会,我多想好好抱着这些看上一天半天。

https://buildingrecommenders.wordpress.com/

https://buildingrecommenders.wordpress.com/2015/11/16/overview-of-recommender-algorithms-part-1/

Overview of Recommender Algorithms – Part 1

Choosing the right algorithm for your recommender is an important decision to make.  There are a lot of algorithms available and it can be difficult to tell which one is appropriate for the problem you’re trying to solve.  Each algorithm has its pros and cons as well as constraints that you would want to have a feeling for before you decide which one to use.  In practice, you will probably test out several algorithms in order to discover which one works best for your users and it will help to have strong intuition about what they are and how they work.

Recommender algorithms are typically implemented in the recommender model (2), which is responsible for taking data, such as user preferences and descriptions of the items that can be recommended, and predicting which items will be of interest to a given set of users.

There are four main families of recommender algorithms (Tables 1-4):

  • Collaborative Filtering
  • Content-based Filtering
  • Hybrid Approaches
  • Popularity

There’s also a number of advanced or non-traditional approaches (Table 5).

This is the first in a multi-part post.  In this post, we’ll introduce the main types of recommender algorithms by providing a cheatsheet for them. It includes a brief description of the algorithm, its typical input, common forms that it can take and its pros and cons.   In the second and third posts, we’ll then describe the different algorithms in more detail, to give a deeper understanding for how they work. Some of the content in this blog post is based on a RecSys 2014 tutorial, The Recommender Problem Revisited, by Xavier Amatriain.

CFProsCons
Table 1: Overview of Collaborative Filtering
CBProsCons.png
Table 2: Overview of Content-based Filtering
HybridProsCons
Table 3: Overview of Hybrid Approaches
PopularityProsCons
Table 4: Overview of Popularity
OtherProsCons
Table 5: Overview of Advanced or “Non-traditional” Approaches

https://buildingrecommenders.wordpress.com/2015/11/18/overview-of-recommender-algorithms-part-2/

Overview of Recommender Algorithms – Part 2

This is the second in a multi-part post. In the first post, we introduced the main types of recommender algorithms by providing a cheatsheet for them. In this post, we’ll describe collaborative filtering algorithms in more detail and discuss their pros and cons in order to give a deeper understanding for how they work.

Collaborative filtering (CF) algorithms look for patterns in user activity to produce user specific recommendations. They depend on having user usage data in a system, for example user ratings on books they have read indicating how much they liked them. The key idea is that the rating of a user for a new item is likely to be similar to that of another user, if both users have rated other items in similar way. It is worth noting that they do not depend on having any additional information about the items (e.g. description, metadata, etc) or the users (e.g. interests, demographic data, etc) in order to generate recommendations. Collaborative filtering approaches can be divided into two categories: neighbourhood and model-based methods. In neighbourhood methods (aka memory-based CF), the user-item ratings are directly used to predict ratings for new items. In contrast, model-based approaches use the ratings to learn a predictive model based on which predictions on new items are made. The general idea is to model the user-item interactions using machine learning algorithms which find patterns in the data.

Neighbourhood-based CF methods focus on the relationship between items (item-based CF), or alternatively, between users (user-based CF).

  • User-Based CF finds users who have similar appreciation for items as you and recommends new items based on what they like.
  • Item-based CF recommends items that are similar to the ones the user likes, where similarity is based on item co-occurrences (e.g. users who bought x, also bought y).

First, we’ll look at user-based collaborative filtering with a worked example before doing the same for the item-based version.

Say that we have some users who have expressed preferences for a range of books.  The more that they like the book, the higher a rating they give it, from a scale of one to five.  We can represent their preferences in a matrix, where the rows contain the users and the columns the books (Figure 1).

user-preferences
Figure 1: User preferences for books.  All preferences are on a scale of 1-5, 5 being the most liked.  The first user (row 1) has a preference for the first book (column 1) given by a rating of 4.  Where a cell is empty, the user has not give a preference for the book.

In user-based collaborative filtering, the first thing that we want to do is to calculate how similar users are to one another based on their preferences for the books.  Let’s consider this from the perspective of a single user, who appears in the first row in Figure 1.  To do this, it’s common to represent every user as a vector (or array) that contains the user preferences for items.  It’s quite straight-forward to compare users with one another using a variety of similarity metrics.  In this example, we’re going to use cosine similarity.  When we take the first user and compare them to the five other users, we can see how similar the first user is to the rest of them (Figure 2).  As with most similarity metrics, the higher the similarity between vectors, the more similar they are to one another.  In this case, the first user is quite similar to two users, as they share two books in common, less similar to two other users, who share just one book in common and not at all similar to the last user with whom they share no books in common.

user-based-similarity-single-user
Figure 2: The similarity between the first user and the rest of the users.  This can be plotted in a single dimension using the cosine similarity between users.

More generally, we can calculate how similar each user is to all users and represent them in a similarity matrix (Figure 3).  This is a symmetric matrix which, as an aside, means that it has some useful properties for performing mathematical functions on it.  The background colour of the cells indicates how similar the users are to one another, the deeper red they are the more similar.

user-based-similarity-full
Figure 3: Similarity matrix between users.  Each user similarity is based on the cosine similarity between the books that the users read.

Now we’re ready to generate recommendations for users, using user-based collaborative filtering.  In general, for a given user, this means finding the users who are most similar to them, and recommending the items that these similar users appreciate, weighting them by how similar the users are.  Let’s take the first user and generate some recommendations for them.  First, we find the top n users who are most similar to the first user, remove books that the user has already given preferences for, weight the books that the most similar users are reading, and sum them together.  In this case, we’ll take n = 2, that is the two users who are most similar to the first user, in order to generate the recommendations.  These two users are users 2 and 3 (Figure 4).  Since, the first user already has rated books 1 and 5, the recommendations generated are books 3 (score of 4.5) and 4 (score of 3).

user-based-generation
Figure 4: Generating recommendations for a user.  We take the books that they two most similar users are reading, weight them, and recommend the books that are not yet rated by the user.

So now that we have a better feeling for user user-based collaborative filtering, let’s look through an example of item-based collaborative filtering. Again, we start with the same set of users who have expressed preferences for books (Figure 1).

In item-based collaborative filtering, similar to user-based, the first thing that we want to do is to calculate a similarity matrix.  This time round, however, we want to look at similarities with respect to items rather than users.  Similarly, we show how similar a book is to other books if we were to represent books as vectors (or arrays) of users who appreciate them and to compare them with the cosine similarity function.  The first book, in column one, is most similar to the fifth book, in column five, as it is appreciated roughly the same by the same set of users (Figure 5).  The third most similar book is appreciated by two of the same users, the fourth and second books only have one user in common while the last book is not considered to be similar at all as it has no users in common.

item-based-similarity-single-user
Figure 5: Comparison of the first book to the rest of them.  Books are represented by the users who have rated them.  Comparisons are made using the cosine similarity metric (0-1).  The higher the similar, the more similar the two books.

More fully, we can show how similar all books are to one another in a similarity matrix (Figure 6).  Again, the background colour of the call shows how similar two books are to one another, the darker the red, the more similar they are.

item-based-similarity-full
Figure 6: Similarity matrix for books.

Now that we know how similar the books are to one another, we can generate recommendations for users.  In the item-based approach, we take the items that a user has previously rated, and recommend items that are most similar to them.  In our example, the first user would be recommended the third book followed by the sixth book (Figure 7).  Again, we only take the top two most similar books to the books that the user has previously rated.

item-based-generation
Figure 7: Generating recommendations for a user.  We take the books that they have rated, find the two most similar books to each of them, weigh them and recommend the books that the user has not yet rated.

Given that the descriptions of user-based and item-based collaborative filtering sound very similar to one another, it’s interesting to note that they can generate different results.  Even in the toy example that we give here, the two approaches generate different recommendations for the same user even though the input is the same to both.  It’s worth considering both of these forms of collaborative filtering when you’re building your recommender.  Although when describing them to non-experts they can sound very similar, in practice they can give quite different results with qualitatively different experience for the user.

Neighbourhood methods enjoy considerable popularity due to their simplicity and efficiency, and their ability to produce accurate and personalised recommendations. However, they also have some scalability limitations as they require a similarity computation (between users or items) that grows with both the number of users and the number of items. In the worst case this computation can be O(m*n), but in practice the situation is slightly better with O(m+n), partly due to exploiting the sparsity of the data. While sparsity helps with scalability, it also presents a challenge for neighbourhood-based methods because we only have user ratings for small percentage of the large number of items. For example, at Mendeley we have millions of articles and a user may have read a few hundred of these articles. The probability that two users who have each read 100 articles have an article in common (in a catalogue of 50 million articles) is 0.0002.

Model-based CF approaches can help overcome some of the limitations of neighbourhood-based methods. Unlike neighbourhood methods which use the user-item ratings directly to predict ratings for new items, model-based approaches use the ratings to learn a predictive model based on which predictions on new items are made. The general idea is to model the user-item interactions using machine learning algorithms which find patterns in the data. In general, model-based CF are considered more advanced algorithms for building CF recommenders. There are a number of different algorithms that can be used for building the models based on which to make predictions, for example, bayesian networks, clustering, classification, regression, matrix factorisation, restricted boltzmann machines, etc. Some of these techniques played a key role in the final solutions for winning the Netflix Prize. Netflix ran a competition from 2006 to 2009 offering $1mil grand prize to the team that can generate recommendations that were 10% more accurate than their recommender system at the time. The winning solution was an ensemble (i.e. mixture) of over 100 different algorithm models of which matrix factorisation and restricted boltzmann machines were adopted in production by Netflix.

Matrix factorisation (e.g. singular value decomposition, SVD++) transforms both items and users to the same latent space which represents the underlying interactions between users and items (Figure 8). The intuition behind matrix factorisation is that the latent features represent how users rate items. Given the latent representations of the users and the items, we can then make predictions of how much the users will like items they have not yet rated.

matrix-factorisation
Figure 8: Representation of matrix factorisation.  A user preference matrix can be decomposed into a user-topic matrix multiplied by a topic-item matrix.

In Table 1, we outlined the key pros and cons of both neighbourhood and model-based collaborative filtering approaches. Since they depend only on having usage data of the users, CF approaches require minimal knowledge engineering efforts to produce good enough results, however, they also have limitations. For example, CF tends to recommend popular items, making it hard to recommend items to someone with unique tastes (i.e. interested in items which may not get so much usage data). This is known as the popularity bias and it is usually addressed with content-based filtering methods. An even more important limitation of CF methods is what we call “the cold start problem”, where the system is not able to give recommendations for users who have no (or very little) usage activity, aka new user problem, or recommend new items for which there is no (or very little) usage activity, aka new item problem. The new user “cold start problem” can be addressed via popularity and hybrid approaches, whereas new item problem can be addressed using content-based filtering or multi-armed bandits (i.e. explore-exploit). We’ll discuss some of these methods in the next post.

In this post we covered three basic implementations of collaborative filtering.  The differences between item-based, user-based and matrix factorisation are quite subtle and it’s often tricky to explain them succinctly.  Understanding the differences between them will help you to choose the most appropriate algorithm for your recommender.  In the next post we’ll continue to look at popular algorithms for recommenders in more depth.

https://buildingrecommenders.wordpress.com/2015/11/19/overview-of-recommender-algorithms-part-3/

Overview of Recommender Algorithms – Part 3

This is the third in a multi-part post. In the first post, we introduced the main types of recommender algorithms by providing a cheatsheet for them. In the second post, covered the different types of collaborative filtering algorithms highlighting some of their nuances and how they differ from one another. In this blog post, we’ll describe content-based filtering in more detail and discuss its pros and cons in order to give a deeper understanding of how it works.

Content-based filtering recommends items that are similar to the ones the user liked in the past. It differs from collaborative filtering, however, by deriving the similarity between items based on their content (e.g. title, year, description) and not how people use them. For example, if a user likes “Lord of the Rings: The Fellowship of the Ring” and “Lord of the Rings: Two Towers” then using the words in the title, the recommender may suggest “Lords of the Rings: The Return of the King”. In content-based filtering, rich information describing each item is assumed to be available in the form of a feature vector (y) (e.g. title, year, description). These feature vectors are used to create a model of the user’s preferences. A variety of information retrieval (e.g. tf-idf) and machine learning techniques (e.g. Naive Bayes, support vector machines, decision trees, etc) can be used to generate a user model based on which recommendations can be generated.

Say that we have some users who have expressed preferences for a range of books.  The more that they like the book, the higher a rating they give it, from a scale of one to five.  We can represent their preferences in a matrix, where the rows contain the users and the columns the books (Figure 1).

Screen Shot 2015-11-18 at 22.27.08
Figure 1: User preferences for books. All preferences are on a scale of 1-5, 5 being the most liked. The first user (row 1) has a preference for the first book (column 1) given by a rating of 4. Where a cell is empty, the user has not given a preference for the book.

In content-based filtering, the first thing that we want to do is to calculate how similar books are to one another based on their content. In our example, we will use the words in the titles of the books (Figure 2). This is just to simplify how a content-based solution works.  In practice, you would use many more attributes.

Screen Shot 2015-11-18 at 22.27.17
Figure 2: Titles of the books users have rated.

First, it’s common to remove the stop words (e.g. grammar words, very common words) from the content and then to represent the books as a vector (or array) that indicates which words are present (Figure 3). This is known as a vector-space representation.

Screen Shot 2015-11-18 at 22.27.24
Figure 3: Vector representations of the books using the words in their titles. We have 1 in the cells when the word is present in the title, and empty otherwise.

Given this representation of each book it’s quite straight-forward to compare books with one another using a variety of similarity metrics. In this example, we’re going to use cosine similarity.  When we take the first book and compare them to the five other books, we can see how similar the first book is to the rest of them (Figure 4).  As with most similarity metrics, the higher the similarity between vectors, the more similar they are to one another. In this case, the first book is quite similar to 3 of the books, with which it shares 2 words (recommender and systems).  It is most similar to the book with fewer words, which makes sense, as it has fewer extra words not in common.  It is not at all similar to the other 2 books with which it has no words in common.

Screen Shot 2015-11-19 at 15.09.45.png
Figure 4: The similarity between the first book and the rest of the books. This can be plotted in a single dimension using the cosine similarity between books.

More fully, we can show how similar all books are to one another in a similarity matrix (Figure 5). The background colour of the cell shows how similar two books are to one another, the darker the red, the more similar they are.

Screen Shot 2015-11-18 at 22.27.50.png
Figure 5: Similarity matrix between books. Each similarity is based on the cosine similarity between the vector representations of the books.

Now that we know how similar the books are to one another, we can generate recommendations for users. Similarly to the item-based CF approach we described in the previous post, we take the books that a user has previously rated, and recommend other books that are most similar to them. The difference is that here the similarity is based on the content of the books, precisely the titles, rather than on the usage data.  In our example, the first user would be recommended the sixth book followed by the fourth book (Figure 6). Again, we only take the top two most similar books to the books that the user has previously rated.

Screen Shot 2015-11-18 at 22.27.57.png
Figure 6: Generating recommendations for a user. We take the books that they have rated, find the two most similar books to each of them, weigh them and recommend the books that the user has not yet rated.

Content-based approaches overcome some of the limitations of collaborative filtering.  In particular, they help you to overcome the popularity bias and new item cold start problem, which we already discussed in the section on collaborative filtering. However, it is worth noting that recommenders purely based on content generally don’t perform as well as ones based on usage data (e.g. collaborative filtering). Content-based filtering also suffer from over-specialisation, where the user might get too many of the same types of items (e.g. being recommended all of the “Lord The Rings” movies) and fail to recommend items that are different but still might be interesting to the user.  Finally, content-based implementations that only use the words contained in item metadata (e.g. title, description year), will tend to bring back more of the same content, which limits its usefulness for help users to explore and discover content that’s outside of that vocabulary.  See Table 2 for a summary of the pros and cons of content-based filtering.

https://buildingrecommenders.wordpress.com/2015/11/20/overview-of-recommender-algorithms-part-4/

Overview of Recommender Algorithms – Part 4

This is the fourth in a multi-part post. In the first post, we introduced the main types of recommender algorithms by providing a cheatsheet for them. In the second post, we covered the different types of collaborative filtering algorithms highlighting some of their nuances and how they differ from one another. In the third post, we described content-based filtering in more detail. In this blog post, we’ll present hybrid recommenders which build upon the algorithms we’ve discussed thus far. We’ll also briefly discuss how the popularity of items can be used to address some of the limitations of collaborative and content-based filtering approaches.

Hybrid Approaches combine user and item content features and usage data to benefit from both types of data. A hybrid recommender combining algorithms A and B tries to use the advantages of A to fix the disadvantages of B. For example, CF algorithm suffer from new-item problems, i.e., they cannot recommend items that have no ratings/usage. This does not limit content-based algorithms since the prediction for new items is based on their content (features) that are typically available when the new-item enters the system. By creating a hybrid recommender which combines collaborative filtering and content-based filtering, we can overcome some of the limitations of the individual algorithms such as cold-start problem and popularity bias.  We outline some of the different ways for combining two (or more) basic RSs techniques to create a new hybrid system in Table 1.

Screen Shot 2015-11-20 at 21.15.27
Table 1: Different ways of combining two (or more) basic recommender algorithms to create a new hybrid algorithm.

Say that we have some users who have expressed preferences for a range of books.  The more that they like a book, the higher a rating they give it, on a scale of one to five.  We can represent their preferences in a matrix, where the rows contain the users and the columns the books (Figure 1).

Screen Shot 2015-11-18 at 22.27.08
Figure 1: User preferences for books. All preferences are on a scale of 1-5, 5 being the most liked. The first user (row 1) has a preference for the first book (column 1) given by rating of 4. Where a cell is empty, the user has not given a preference for the book.

Using this setup, in Part 2 of this posts’ series, we worked through two examples showing how to compute recommendations using item- and user-based collaborative filtering algorithms, and in Part 3 we showed how to use content-based filtering to generate recommendations. We’ll now combine these three different algorithms to create a new hybrid recommender. We’ll use the weighted method (Table 1) which combines the output from several techniques, in this case three, with different degrees of importance (i.e. weight) to offer new set of recommendations.

Let’s take the first user and generate some recommendations for them. First we get the recommendations from user- and item-based collaborative filtering (CF) from Part 2 and content-based filtering (CB) from Part 3 (Figure 2). It is worth noting that on this small toy example, the three approaches generate slightly different recommendations for the same user even though the input is the same to all three of them.

Screen Shot 2015-11-20 at 17.49.07
Figure 2: Recommendations for a user from user-based CF, item-based CF and content-based filtering.

Next, we generate recommendations for the given user using a weighted hybrid recommender by putting 40% of the weight on user-based CF, 30% on item-based CF and 30% on content-based filtering (Figure 3). In our example, the user would be recommended all three books that they have not rated yet, compared to getting just two book recommendations from the individual algorithms.

Screen Shot 2015-11-20 at 17.49.19.png
Figure 3: Generating recommendations for a user using a weighted hybrid recommender by putting 40% weight on user-based CF, 30% on item-based CF and 30% on content-based filtering.

Although hybrid approaches address some of the big challenges and limitations of CF and CB methods (see Table 3), they also require a lot of work to get the right balance between the different algorithms in the system. Another way of combining individual recommender algorithms is using ensemble methods, where we learn a function (i.e. train an ensemble) for how to mix the results of the different methods. It is worth noting that usually ensembles combine not only different algorithms, but also different variations/models based on the same algorithm. For example, the winning solution in the Netflix Prize consisted of over 100 different models from more than 10 different algorithms (popularity, neighbourhood methods, matrix factorisation, restricted boltzmann machines, regression and more), which were combined in an ensemble using gradient boosted decision trees.

It’s also worth adding that popularity-based approaches are a good solution to the new user cold start problem.  These approaches rate items using some form of popularity measure such as most downloaded or purchased, and recommend these popular items to new users.  It’s a basic but powerful approach when you have a good measure of popularity and often provides a good baseline with which to compare other recommender algorithms. Popularity can be used on its own as an algorithm to bootstrap a recommender system to get enough activity and usage for the user before switching to approaches that can better model user interests such as collaborative filtering and content-based filtering.  Popularity models can also be included in hybrid approaches, allowing them to address the new user cold start problem.

https://buildingrecommenders.wordpress.com/2015/11/23/overview-of-recommender-algorithms-part-5/

Overview of Recommender Algorithms – Part 5

This is the final part in a five part series on overviewing recommender algorithms. In the first post, we introduced the main types of recommender algorithms by providing a cheatsheet for them. In the second, we covered the different types of collaborative filtering algorithms highlighting some of their nuances and how they differ from one another. In the third, we described content-based filtering in more detail. In the fourth, we presented hybrid recommenders and popularity-based approaches.  In this post we’ll conclude the series by adding some words about a selection of advanced recommender algorithms before looking back at how different the output of the basic recommender algorithms can be.

In addition to the more traditional recommender systems approaches we’ve covered so far (e.g. popularity, collaborative filtering, content-based filtering, hybrid approaches), there are a number of other methods that can also be used to power recommender systems including:

  • Deep learning
  • Social recommendations
  • Learning to rank
  • Multi-armed bandits (explore/exploit)
  • Tensor Factorisation and Factorisation Machines (context-aware recommendations)

These more advanced and non-traditional methods are good for taking the quality of your recommenders to the next level, but are also less understood and not as well supported in recommendation toolkits.  In practice you always need to decide whether the cost of implementing advanced methods is worth the added value that it will bring compared to some more basic approaches.  In our experience, the basic algorithms can take you a long way and be used to power some great products.

In this series, we wanted to introduce you to a number of common recommender module algorithms including user-based collaborative filtering, item-based collaborative filtering, content-based filtering and hybrid methods.   Here, we provided a single illustration that shows how these four different algorithms, when applied to the same toy example of input data, will generate different recommendations for the same user (Figure 1).  This effect also holds when applied to large, real-world data, so deciding which algorithm to use will be a matter of considering their relative pros and cons and how well they perform when youevaluate them.

Comparison of Recommender System Algorithms
Figure 1: Overview of four recommender system algorithms all being applied to the same set of data and giving different results. One the left, we have the user preferences for a number of items in a matrix and the list of titles of the items that can be recommended. In the middle we show how four different algorithms generate recommendations for the first user (i.e. row 1 in the user preferences matrix). They have different definitions of similarity, as shown by the similarity matrices. On the right, we see the items generated by each recommender algorithm, maintaining order from top to bottom, consistent with the algorithm ordering.

In practice, you generally won’t go far wrong if you can use collaborative filtering as the algorithm in your recommender model.  Collaborative filtering tends to outperform the alternatives but, given its trouble dealing with cold start users and items, it’s common to rely on a content-based algorithm as a fallback.  If you have the time then it’s great to put together a hybrid so that you can get the best of both collaborative filtering and content-based filtering.  It’s certainly a good idea to put these basic algorithms in place before even thinking about looking at the advanced ones.

Finally, it’s worth keeping in mind that the recommender model is just one of fiverecommender systems components.  Like all components, it is important to set it up correctly and to put effort into it but the choices that you make about your data collection and processing, post-processing, online modules and user interface are just as important.  As we’ll emphasise over and over again, the algorithm is just one part of the recommender and your decisions need to be taken thinking about the entire product.

900 total views, no views today

[转]2016 的技术趋势

http://www.frogdesign.com/techtrends2016/

 

Tech Trends 2016

With the start of a new year, the exciting question is: What technology trends will radically transform businesses in 2016 and beyond? At frog we help our clients digitally transform their businesses by identifying emerging technologies and realizing their potential. Here are 15 technology trends driving companies to reinvent themselves.

BLOCKCHAIN BEYOND BITCOIN

DATA-DRIVEN DESIGN TAKES CENTER STAGE

MICROBIOME MAKES HEALTH PERSONAL

AI SAVES FINANCIAL SERVICES

VR MEDICAL THERAPY

FDA-APPROVED VIDEO GAMES

HUMAN-CENTERED DESIGN IS AUTOMATED

VR BREAKS DOWN BORDERS

919 total views, no views today