• 一些实用的 Laravel 小技巧

    Laravel 中一些常用的小技巧,额,说不定你就用上了。。。

    1.侧栏

    网站一般都有侧栏,用来显示分类,标签,热门文章,热门评论啥的,但是这些侧栏都是相对独立的模块,如果在每一个引入侧栏的视图中都单独导入与视图有关的数据的话,未免太冗余了。。。所以最佳的做法是:新建一个widgets视图文件夹,再利用Laravel 的ViewComposers单独为侧栏绑定数据,这样侧栏就可以随便引入而不用关心数据是否绑定啦~~~

    举个栗子?拿最常用的分类侧栏来说,在resources/views/widgets下新建你的分类侧栏视图文件categories.blade.php

    <div class="widget widget-default">
        <div class="widget-header"><h6><i class="fa fa-folder fa-fw"></i>分类</h6></div>
        <ul class="widget-body list-group">
            @forelse($categories as $category)
                @if(str_contains(urldecode(request()->getPathInfo()),'category/'.$category->name))
                    <li href=""
                        class="list-group-item active">
                        
                        <span class="badge"></span>
                    </li>
                @else
                    <a href=""
                       class="list-group-item">
                        
                        <span class="badge"></span>
                    </a>
                @endif
            @empty
                <p class="meta-item center-block">No categories.</p>
            @endforelse
        </ul>
    </div>
    

    新建app/Http/ViewComposers文件夹,然后创建CategoriesComposer.php

    <?php
    namespace App\Http\ViewComposers;
    use App\Http\Repositories\CategoryRepository;
    use Illuminate\View\View;
    class CategoriesComposer
    {
        public function __construct(CategoryRepository $categoryRepository)
        {
            $this->categoryRepository = $categoryRepository;
        }
    
        public function compose(View $view)
        {
            $categories = $this->categoryRepository->getAll()->reject(function ($category) {
                return $category->posts_count == 0;
            });
            $view->with('categories', $categories);
        }
    }
    

    再在app/Providers文件夹下新建ComposerServiceProvider.php文件:

    <?php
    namespace App\Providers;
    use Illuminate\Support\ServiceProvider;
    use Illuminate\Support\Facades\View;
    class ComposerServiceProvider extends ServiceProvider
    {
    
        public function boot()
        {
            View::composer('widget.categories', 'App\Http\ViewComposers\CategoriesComposer');
        }
    
        public function register(){}
    }
    

    最后别忘了在config/app.php中的providers数组中添加App\Providers\ComposerServiceProvider::class啊。好了,现在你可以随时随地@include('widget.categories')了。对了,要善于在ViewComposer中利用Collection强大方法进行数据处理幺~~

    2.善用路由别名

    Laravel 最让人喜欢的地方之一是可以给路由起一个别名,比如:

    Route::get('user/profile', '[email protected]')->name('user.profile');
    // 等价于:
    Route::get('user/profile', ['uses' => '[email protected]' , 'as' => 'user.profile']);;
    

    然后,就可以在试图中就可以使用route()方法引用了:

    // 例如:
    <a href="">lufficc</a>
    

    因为一个普通的项目路由至少也得有几十个,如果使用url()方法的话,你不但要记住具体的路由,更麻烦的是如果你将来想要改变某个路由(比如把'user/profile'改为'u/profile',或者加个前缀啥的),必须改变所有相关的视图文件,这。。。这。。。不敢相信,而使用命名路由的话,只要命名不变,毫不受影响。

    所以视图文件中尽量避免使用url()方法,为每一个路由命名,一个默认的命名规则为:资源名称.<属性>或者<动作>,如post.showimage.upload

    3.全局动态设置

    仅仅是.env的配置还无法满足我们的需求,有时我们需要可以在后台动态的进行一些设置,比如网站的标题,网站的背景图片或者是否允许评论等等。那么实现这个的最佳实践是什么?

    熟悉wordpress的同学知道,wordpress可以进行很多自定义,因为wordpress有一张键值对数据库表,它就是靠这个实现个性化的。因此我们也可以参考这种思路,增加一个键值对表,以Xblog为例子,新建一个maps表:

    Schema::create('maps', function (Blueprint $table) {
           $table->increments('id');
           $table->string('key')->unique();
           $table->string('tag')->index();
           $table->text('value')->nullable(true);
    });
    

    maps表的作用就是实现键值对key-value存储,tag的是为了可以有一个分类。然后后台进行存储的话,不要写死,这样就可以随时在变单中添加设置而无需更改代码:

    $inputs = $request->except('_token');
    foreach ($inputs as $key => $value) {
                $map = Map::firstOrNew([
                    'key' => $key,
                ]);
                $map->tag = 'settings';
                $map->value = $value;
                $map->save();
    }
    

    注意firstOrNew的用法:如果不存在这个选项我们就新增一个并保存,否则就更新它。然后我们就可以在视图中随便增加任意多个表单了(或者也可以用js动态生成表单)。有了数据,怎么在视图中利用呢?利用ViewComposer,新建一个SettingsComposer.php,然后将查询的数据以数组的形式传递给试图:

    //在SettingsComposer.php的compose方法中绑定数据
    public function compose(View $view)
    {
        $settings = Map::where('tag', 'settings')->get();
    	$arr = [];
        foreach ($settings as $setting) {
          $arr[$setting->key] = $setting->value;
        }
       $view->with($arr);
    }
    

    然后就可以在视图中随便引用了,如你表单新增加了一个description

    <input type="text" name="description" value="">
    

    然后就可以在任何视图引用了:``。另外还可以绑定一个单例Facades到容器,这样就可以在代码中随时获取配置信息啦~~~ 比如:

    //1.注册
    public function register()
    {
        $this->app->singleton('XblogConfig', function ($app) {
           return new MapRepository();
       });
    }
    //2.注册Facade
    class XblogConfig extends Facade
    {
        public static function getFacadeAccessor()
        {
            return 'XblogConfig';
        }
    }
    //3.添加到aliases数组
    
    
    'aliases' => [
    
            *****************  省略  *************************
            'XblogConfig' => App\Facades\XblogConfig::class,
        ],
    		
    
    //4.愉快的使用,可爽
    $page_size = XblogConfig::getValue('page_size', 7);
    
    

    4.数据库查询

    怎么统计一篇文章有多少评论?最快的方法是:

    $post = Post::where('id',1)->withCount('comments')->first();
    

    这样$post变量就有一个属性comments_count了:

    $post->comments_count;
    

    如果想获取点赞数大于的100的评论个数怎么办?这样:

    $post = Post::where('id',1)->withCount('comments',function($query){
           $query->where('like', '>', 100);
       })->first();
    

    简单吧~~

    5.多态关联

    文章可以有评论,页面可以有评论,评论也可以有评论,但是总不能建三张评论表吧?如果自己写条件判断也太麻烦了吧。。。Laravel的多态关联上场了!!

    //1.第一步在Comment模型中说明我是可以多态的
    public function commentable()
    {
        return $this->morphTo();
    }
    
    //2.在想要评论的模型中增加comments方法,
    public function comments()
    {
        return $this->morphMany(Comment::class, 'commentable');
    }
    
    //3.使用,就像普通的一对多关系一样:
    $model->comments;
    

    原理很简单,comments表中增加两个列就行:

    Schema::create('comments', function (Blueprint $table) {
         ***************省略*******************
         $table->morphs('commentable');
         //等价于
         $table->integer('commentable_id')->index();
         $table->string('commentable_type')->index();
        ****************省略******************
    });
    

    然后 laravel 会自动维持这些关系。注意,保存的评论的时候是有小技巧的,你的表单中至少要传两个参数:commentable_idcommentable_type

    $comment = new Comment();
    
    $commentable_id = $request->get('commentable_id');
    //commentable_type取值例如:App\Post,App\Page等等
    $commentable = app($request->get('commentable_type'))->where('id', $commentable_id)->firstOrFail();
    
    ****************省略******************
    
    $commentable->comments()->save($comment);
    

    保存评论的时候并不知道是谁的评论,而是使用容器根据commentable_type生成一个模型实例,这样也就和具体的模型解耦了,你可以让任何东西可以评论,而不需要修改代码。

    6.缓存优化相关

    如果你想要在.env文件中添加自己的配置,记住一定要在config文件夹下某个配置文件的数组中添加对应的。记住,除了config文件夹下的配置文件,永远不要在其它地方使用env函数,因为部署到线上时,配置文件缓存(php artisan config:cache)后,env函数无法获得正确的值。

    另外注意的是,路由文件中尽量不使用闭包函数,统一使用控制器,因为缓存路由的时候php artisan route:cache,无法缓存闭包函数。

    7.Redis

    如果你缓存使用Redis,session也使用了Redis,队列已使用了Redis,这样没问题,速度很快,但是!!当你运行php artisan cache:clear清除缓存时,会把你的登录信息清除,也会把队列清除。。。这就不优雅了。解决办法很简单,为它们分配不同的连接即可。 首先在config\database.php中增加连接,注意database序号:

    'redis' => [
    
            'cluster' => false,
    
            'default' => [
                'host' => env('REDIS_HOST', 'localhost'),
                'password' => env('REDIS_PASSWORD', null),
                'port' => env('REDIS_PORT', 6379),
                'database' => 0,
            ],
            'session' => [
                'host' => env('REDIS_HOST', 'localhost'),
                'password' => env('REDIS_PASSWORD', null),
                'port' => env('REDIS_PORT', 6379),
                'database' => 1,
            ],
            'queue' => [
                'host' => env('REDIS_HOST', 'localhost'),
                'password' => env('REDIS_PASSWORD', null),
                'port' => env('REDIS_PORT', 6379),
                'database' => 2,
            ],
    
        ],
    

    然后分别为sessionqueue更换连接:

    //queue.php中的connections数组中:
    'redis' => [
                'driver' => 'redis',
                'connection' => 'queue',
                'queue' => 'default',
                'retry_after' => 90,
            ],
    				
    //session.php中的connection选项:
    'connection' => 'session',
    

    这样他们就互不相干了~~

    以上经验来自 Xblog,示例均可以在 Xblog 找到

  • 堆排序以及最大优先队列

    堆排序(heapsort)是一种比较快速的排序方式,它的时间复杂度为O(nlgn),而且堆排序具有空间原址性:即任何时候只需要有限(常数个)的空间来存储临时数据。而且堆排序还被应用在构造优先级队列中,本文将会用Java实现一个最大堆,并利用最大堆实现优先级队列。

    最大堆的性质

    1. 是一棵近似的完全二叉树,除了最底层,其它是全满,且从左向右填充。
    2. 树的每个节点对应数组一个元素,根节点对应数组下标0元素。
    3. 对于下标i,它的父节点下标为(i + 1) / 2 - 1,左孩子节点下标为i * 2 + 1,右孩子节点下标为i * 2 + 2
    4. 最大堆中,每个结点都必须大于等于左右孩子节点。

    file

    file

    堆排序

    1.维护最大堆

    为了建立一个最大堆,我们需要设计一个算法来维护最大堆的性质。对于节点i,我们假设它们的左右子树都是最大堆,而节点i因为小于左右孩子而违背了最大堆的性质,因此我们需要对节点i进行逐级下降,从而使得以节点i为根结点的子树满足最大堆的性质。

    private void maxHeapify(int[] a, int i, int length) {
        int l = left(i);
        int r = right(i);
        int max;
    		if (l < length && a[l] > a[i]) {
            max = l;
        } else {
            max = i;
        }
        if (r < length && a[r] > a[max]) {
            max = r;
        }
        if (max != i) {
            swap(a, i, max);
            maxHeapify(a, max, length);
        }
      }
    

    上述代码的解释是: 对于节点i(即数组下标i),首先获取它的左右子孩子下标,分别存储到lr变量中,其中leftright函数可以根据最大堆的性质得到,如下:

        private int left(int i) {
            return i * 2 + 1;
        }
    
        private int right(int i) {
            return i * 2 + 2;
        }
    

    变量length是要维护的数组的长度,它的值<=数组的真实长度。临时变量max用来存储节点i左孩子右孩子这三者最大者的下标。因此,首先比较左孩子a[l]是否大于节点i,是的话将左孩子下标l赋值给max,否则,当前最大者仍为节点i,并把i的值赋给max。然后当前最大者和右孩子比较,小于右孩子的话就把下标r赋值给max。到目前为止,我们找到了节点i左孩子右孩子这三者之中的最大者的下标max,如果节点i本来就是最大的,那么就不需要维护了,所以函数结束。否则的话,有孩子节点大于节点i,因此我们需要将节点i和较大的那个孩子进行交换,即swap(a, i, max)swap函数如下:

        private void swap(int[] a, int i, int j) {
            int tmp = a[i];
            a[i] = a[j];
            a[j] = tmp;
        }
    

    交换之后,节点max的值变成了较小的原来节点i的值,因此,以节点max为根节点的子树可能违背最大堆的性质。注意到此时问题和一开始一模一样,只是节点i变成了节点max,因此递归调用maxHeapify(a, max, length)即可。

    如果把第一张图的节点1的值改为1,这时从节点1开始调用maxHeapify(a, 1, a.length,整个流程如下:

    file

    file

    file

    可以看到,在经历两次递归调用,逐级下沉,原来因为1节点变小,而被打破的最大堆,又重新维护完成。

    2.建立最大堆

    我们采用自底向上的方法来建立一个最大堆。通过观察发现,下标为a.length / 2a.length - 1的节点均为叶子节点,没有子孩子。这意味着,它们已经是一个最大堆,只是仅有一个元素。因此,我们只需要自底向上,对其他节点调用maxHeapify来维护最大堆即可:

        private void buildMaxHeap(int a[]) {
            for (int i = a.length / 2 + 1; i >= 0; i--) {
                maxHeapify(a, i);
            }
        }
    

    注意,循环变量从a.length / 2 + 1开始,因为a.length / 2a.length - 1的节点满足最大堆,即a.length / 2 + 1的左右子孩子(如果有的话)是最大堆,符合使用maxHeapify调用条件。如果我们对数组[10, 8, 11, 8, 14, 9, 4, 1, 17]进行建立最大堆,那么输出数组为[17, 14, 11, 8, 10, 9, 4, 1, 8]: file

    file

    3.堆排序

    在第二步建立完成最大堆之后,注意到整个数组的** 最大元素 总是在最大堆的第一个,即a[0]这时,如果我们拿走第一个元素,放到数组的最后一个位置,然后对第一个元素进行维护最大堆maxHeapify,如此循环进行,便可完成整个数组的排序**。

        public void heapSort(int[] a) {
            buildMaxHeap(a);//首先建立最大堆,完成后第一个元素为最大值
            int length = a.length;
            for (int i = a.length - 1; i >= 1; i--) {
                swap(a, i, 0);//将第一个最大的元素移到后面,并且在maxHeapify的过程中通过减小length忽略它
                length--;
                maxHeapify(a, 0, length);
            }
        }
    

    这样我们就完成了对数组的从小到大排序。

    最大优先队列

    堆排序是一个优秀的算法,在操作系统中可以利用最大堆实现最大优先队列来实现共享计算机系统的作业调度。最大优先队列记录各个作业之间的相对优先级,当某个作业中断后选出具有最高优先级的队列来执行。最大优先级队列应该支持如下操作:

    1. maximum():返回堆的最大值。
    2. extractMax():返回堆的最大值并从堆中删除。
    3. heapIncreaseKey(i, key):将下标为i的元素增大为key
    4. maxHeapInsert(key):将元素key插入到堆中。

    maximum()最简单,直接返回第一个元素:

        public int maximum() {
            return a[0];
        }
    

    extractMax也很简单,取出第一个后,只需把最后一个元素放到第一个,然后对第一个元素进行维护最大堆即可:

    public int extractMax() {
        int max = a[0];
        a[0] = a[a.length - 1];
        int[] newA = new int[a.length - 1];
        System.arraycopy(a, 0, newA, 0, newA.length);
        maxHeapify(newA, 0, newA.length);
        return max;
    }
    

    file

    heapIncreaseKey(i, key)会增大下标为i的元素为key。首先将a[i]的值更新为key,因为增大的a[i]关键字可能会违背最大堆的性质,因此我们需要对a[i]进行逐级上升。即将当前元素逐级与父节点比较,如果大于父节点,则与父节点进行交换,一直到当前元素小于父节点为止:

    public void heapIncreaseKey(int i, int key) {
        if (key > a[i]) {
            a[i] = key;
            while (i > 0 && a[parent(i)] < a[i]) {  //逐级上升
                swap(a, parent(i), i);
                i = parent(i);
            }
        } else {
            throw new IllegalArgumentException("key is too small");
        }
    }
    		
    private int parent(int i) {
        return (i + 1) / 2 - 1;
    }
    

    maxHeapInsert(key)十分简单,因为它等价于数组长度增加一,然后最后一个元素设置为-∞,然后把它增大为key的操作:

    public void maxHeapInsert(int key) {
        int[] newA = new int[a.length + 1];
        System.arraycopy(a, 0, newA, 0, a.length);
        newA[newA.length - 1] = Integer.MIN_VALUE;
        this.a = newA;
        heapIncreaseKey(a.length - 1, key);
    }
    

    file

    在一个包含n个元素的堆中,所有优先级队列的操作都可以在O(lgn)的时间内完成。 源代码可以在这里找到:lufficc/Algorithm

    文章来自自己的理解以及算法导论

  • Git 的核心概念

    文章内容来自自己的理解 和 https://git-scm.com/book/en/v2 。

    本文不是Git使用教学篇,而是偏向理论方面,旨在更加深刻的理解Git,这样才能更好的使用它,让工具成为我们得力的助手。

    版本控制系统

    Git 是目前世界上最优秀的分布式版本控制系统。版本控制系统是能够随着时间的推进记录一系列文件的变化以便于你以后想要的退回到某个版本的系统。版本控制系统分为三大类:本地版本控制系统,集中式版本控制系统和分布式版本控制系统

    本地版本控制(Local Version Control Systems)是将文件的各个版本以一定的数据格式存储在本地的磁盘(有的VCS 是保存文件的变化补丁,即在文件内容变化时计算出差量保存起来),这种方式在一定程度上解决了手动复制粘贴的问题,但无法解决多人协作的问题。

    本地版本控制{.to-figure}

    集中式版本控制(Centralized Version Control Systems)相比本地版本控制没有什么本质的变化,只是多了个一个中央服务器,各个版本的数据库存储在中央服务器,管理员可以控制开发人员的权限,而开发人员也可以从中央服务器拉取数据。集中式版本控制虽然解决了团队协作问题,但缺点也很明显:所有数据存储在中央服务器,服务器一旦宕机或者磁盘损坏,会造成不可估量的损失。

    集中式版本控制{.to-figure}

    分布式版本控制( Distributed Version Control System)与前两者均不同。首先,在分布式版本控制系统中,像 Git,Mercurial,Bazaar 以及 Darcs 等,系统保存的的不是文件变化的差量,而是文件的快照,即把文件的整体复制下来保存,而不关心具体的变化内容。其次,最重要的是分布式版本控制系统是分布式的,当你从中央服务器拷贝下来代码时,你拷贝的是一个完整的版本库,包括历史纪录,提交记录等,这样即使某一台机器宕机也能找到文件的完整备份。

    分布式版本控制{.to-figure}

    Git基础

    Git是一个分布式版本控制系统,保存的是文件的完整快照,而不是差异变化或者文件补丁。

    保存每一次变化文件的完整内容{.to-figure}

    Git每一次提交都是对项目文件的一个完整拷贝,因此你可以完全恢复到以前的任一个提交而不会发生任何区别。这里有一个问题:如果我的项目大小是10M,那Git占用的空间是不是随着提交次数的增加线性增加呢?我提交(commit)了10次,占用空间是不是100M呢?很显然不是,Git是很智能的,如果文件没有变化,它只会保存一个指向上一个版本的文件的指针,即,对于一个特定版本的文件,Git只会保存一个副本,但可以有多个指向该文件的指针

    另外注意,Git最适合保存文本文件,事实上Git就是被设计出来就是为了保存文本文件的,像各种语言的源代码,因为Git可以对文本文件进行很好的压缩和差异分析(大家都见识过了,Git的差异分析可以精确到你添加或者删除了某个字母)。而二进制文件像视频,图片等,Git也能管理,但不能取得较好的效果(压缩比率低,不能差异分析)。实验证明,一个 500k 的文本文件经Git压缩后仅 50k 左右,稍微改变内容后两次提交,会有两个 50k 左右的文件,没错的,保存的是完整快照。而对于二进制文件,像视频,图片,压缩率非常小, Git 占用空间几乎随着提交次数线性增长。

    未变化的文件只保存上一个版本的指针{.to-figure}

    Git工程有三个工作区域:工作目录,暂存区域,以及本地仓库。工作目录是你当前进行工作的区域;暂存区域是你运行git add命令后文件保存的区域,也是下次提交将要保存的文件(注意:Git 提交实际读取的是暂存区域的内容,而与工作区域的文件无关,这也是当你修改了文件之后,如果没有添加git add到暂存区域,并不会保存到版本库的原因);本地仓库就是版本库,记录了你工程某次提交的完整状态和内容,这意味着你的数据永远不会丢失。 file 相应的,文件也有三种状态:已提交(committed),已修改(modified)和已暂存(staged)。已提交表示该文件已经被安全地保存在本地版本库中了;已修改表示修改了某个文件,但还没有提交保存;已暂存表示把已修改的文件放在下次提交时要保存的清单中,即暂存区域。所以使用Git的基本工作流程就是:

    1. 在工作区域增加,删除或者修改文件。
    2. 运行git add,将文件快照保存到暂存区域。
    3. 提交更新,将文件永久版保存到版本库中。 file

    Git对象

    现在已经明白Git的基本流程,但Git是怎么完成的呢?Git怎么区分文件是否发生变化?下面简单介绍一下Git的基本原理。

    SHA-1 校验和

    Git 是一套内容寻址文件系统。意思就是Git 从核心上来看不过是简单地存储键值对(key-value),value是文件的内容,而key是文件内容与文件头信息的 40个字符长度的 SHA-1 校验和,例如:5453545dccd33565a585ffe5f53fda3e067b84d8。Git使用该校验和不是为了加密,而是为了数据的完整性,它可以保证,在很多年后,你重新checkout某个commit时,一定是它多年前的当时的状态,完全一摸一样。当你对文件进行了哪怕一丁点儿的修改,也会计算出完全不同的 SHA-1 校验和,这种现象叫做“雪崩效应”(Avalanche effect)。

    SHA-1 校验和因此就是上文提到的文件的指针,这和C语言中的指针很有些不同:C语言将数据在内存中的地址作为指针,Git将文件的 SHA-1 校验和作为指针,目的都是为了唯一区分不同的对象。但是当C语言指针指向的内存中的内容发生变化时,指针并不发生变化,但Git指针指向的文件内容发生变化时,指针也会发生变化。所以,Git中每一个版本的文件,都有一个唯一的指针指向它。

    文件(blob)对象,树(tree)对象,提交(commit)对象

    blob 对象保存的仅仅是文件的内容,tree 对象更像是操作系统中的目录,它可以保存blob对象和tree 对象。一个单独的 tree 对象包含一条或多条 tree 记录,每一条记录含有一个指向 blob 对象或子 tree 对象的 SHA-1 指针,并附有该对象的权限模式 (mode)、类型和文件名信息等: file 当你对文件进行修改并提交时,变化的文件会生成一个新的blob对象,记录文件的完整内容(是全部内容,不是变化内容),然后针对该文件有一个唯一的 SHA-1 校验和,修改此次提交该文件的指针为该 SHA-1 校验和,而对于没有变化的文件,简单拷贝上一次版本的指针即 SHA-1 校验和,而不会生成一个全新的blob对象,这也解释了10M大小的项目进行10次提交总大小远远小于100M的原因。

    另外,每次提交可能不仅仅只有一个 tree 对象,它们指明了项目的不同快照,但你必须记住所有对象的 SHA-1 校验和才能获得完整的快照,而且没有作者,何时,为什么保存这些快照的原因。commit对象就是问了解决这些问题诞生的,commit 对象的格式很简单:指明了该时间点项目快照的顶层tree对象、作者/提交者信息(从 Git 设置的 user.name 和 user.email中获得)以及当前时间戳、一个空行,上一次的提交对象的ID以及提交注释信息。你可以简单的运行git log来获取这新信息:

    $ git log
    commit 2cb0bb475c34a48957d18f67d0623e3304a26489
    Author: lufficc <[email protected]>
    Date:   Sun Oct 2 17:29:30 2016 +0800
    
        fix some font size
    
    commit f0c8b4b31735b5e5e96e456f9b0c8d5fc7a3e68a
    Author: lufficc <[email protected]>
    Date:   Sat Oct 1 02:55:48 2016 +0800
    
        fix post show css
    
    ***********省略***********
    

    file 上图的Test.txt是第一次提交之前生成的,第一次它的初始 SHA-1 校验和以3c4e9c开头。随后对它进行了修改,所以第二次提交时生成了一个全新blob对象,校验和以1f7a7a开头。而第三次提交时Test.txt并没有变化,所以只是保存最近版本的 SHA-1 校验和而不生成全新的blob对象。在项目开发过程中新增加的文件在提交后都会生成一个全新的blob对象来保存它。注意除了第一次每个提交对象都有一个指向上一次提交对象的指针

    因此简单来说,blob对象保存文件的内容;tree对象类似文件夹,保存blob对象和其它tree对象;commit对象保存tree对象,提交信息,作者,邮箱以及上一次的提交对象的ID(第一次提交没有)。而Git就是通过组织和管理这些对象的状态以及复杂的关系实现的版本控制以及以及其他功能如分支。

    Git引用

    现在再来看引用,就会很简单了。如果我们想要看某个提交记录之前的完整历史,就必须记住这个提交ID,但提交ID是一个40位的 SHA-1 校验和,难记。所以引用就是SHA-1 校验和的别名,存储在.git/refs文件夹中。

    最常见的引用也许就是master了,因为这是Git默认创建的(可以修改,但一般不修改),它始终指向你项目主分支的最后一次提交记录。如果在项目根目录运行cat .git/refs/heads,会输出一个SHA-1 校验和,例如:

    $ cat .git/refs/heads/master
    4f3e6a6f8c62bde818b4b3d12c8cf3af45d6dc00
    

    因此master只是一个40位SHA-1 校验和的别名罢了。

    还有一个问题,Git如何知道你当前分支的最后一次的提交ID?在.git文件夹下有一个HEAD文件,像这样:

    $ cat .git/HEAD
    ref: refs/heads/master
    

    HEAD文件其实并不包含 SHA-1 值,而是一个指向当前分支的引用,内容会随着切换分支而变化,内容格式像这样:ref: refs/heads/<branch-name>。当你执行 git commit 命令时,它就创建了一个 commit 对象,把这个 commit 对象的父级设置为 HEAD 指向的引用的 SHA-1 值。

    再来说说 Git 的 tag,标签。标签从某种意义上像是一个引用, 它指向一个 commit 对象而不是一个 tree,包含一个标签,一组数据,一个消息和一个commit 对象的指针。但是区别就是引用随着项目进行它的值在不断向前推进变化,但是标签不会变化——永远指向同一个 commit,仅仅是提供一个更加友好的名字。

    Git分支

    分支

    分支是Git的杀手级特征,而且Git鼓励在工作流程中频繁使用分支与合并,哪怕一天之内进行许多次都没有关系。因为Git分支非常轻量级,不像其他的版本控制,创建分支意味着要把项目完整的拷贝一份,而Git创建分支是在瞬间完成的,而与你工程的复杂程度无关。

    因为在上文中已经说到,Git保存文件的最基本的对象是blob对象,Git本质上只是一棵巨大的文件树,树的每一个节点就是blob对象,而分支只是树的一个分叉。说白了,分支就是一个有名字的引用,它包含一个提交对象的的40位校验和,所以创建分支就是向一个文件写入 41 个字节(外加一个换行符)那么简单,所以自然就快了,而且与项目的复杂程度无关。

    Git的默认分支是master,存储在.git\refs\heads\master文件中,假设你在master分支运行git branch dev创建了一个名字为dev的分支,那么git所做的实际操作是:

    1. .git\refs\heads文件夹下新建一个文件名为dev(没有扩展名)的文本文件。
    2. 将HEAD指向的当前分支(当前为master)的40位SHA-1 校验和外加一个换行符写入dev文件。
    3. 结束。

    file

    创建分支就是这么简单,那么切换分支呢?更简单:

    1. 修改.git文件下的HEAD文件为ref: refs/heads/<分支名称>
    2. 按照分支指向的提交记录将工作区的文件恢复至一模一样。
    3. 结束。

    记住,HEAD文件指向当前分支的最后一次提交,同时,它也是以当前分支再次创建一个分支时,将要写入的内容。

    分支合并

    再来说一说合并,首先是Fast-forward,换句话说,如果顺着一个分支走下去可以到达另一个分支的话,那么 Git 在合并两者时,只会简单地把指针右移,因为这种单线的历史分支不存在任何需要解决的分歧,所以这种合并过程可以称为快进(Fast forward)。比如: file 注意箭头方向,因为每一次提交都有一个指向上一次提交的指针,所以箭头方向向左,更为合理

    当在master分支合并dev分支时,因为他们在一条线上,这种单线的历史分支不存在任何需要解决的分歧,所以只需要master分支指向dev分支即可,所以非常快。

    当分支出现分叉时,就有可能出现冲突,而这时Git就会要求你去解决冲突,比如像下面的历史: file 因为master分支和dev分支不在一条线上,即v7不是v5的直接祖先,Git 不得不进行一些额外处理。就此例而言,Git 会用两个分支的末端(v7v5)以及它们的共同祖先(v3)进行一次简单的三方合并计算。合并之后会生成一个和并提交v8file 注意:和并提交有两个祖先(v7v5)。

    分支的变基rebase

    把一个分支中的修改整合到另一个分支的办法有两种:mergerebase。首先mergerebase最终的结果是一样的,但 rebase能产生一个更为整洁的提交历史。仍然以上图为例,如果简单的merge,会生成一个提交对象v8,现在我们尝试使用变基合并分支,切换到dev

    $ git checkout dev
    $ git rebase master
    First, rewinding head to replay your work on top of it...
    Applying: added staged command
    

    file

    这段代码的意思是:回到两个分支最近的共同祖先v3,根据当前分支(也就是要进行变基的分支 dev)后续的历次提交对象(包括v4v5),生成一系列文件补丁,然后以基底分支(也就是主干分支 master)最后一个提交对象(v7)为新的出发点,逐个应用之前准备好的补丁文件,最后会生成两个新的合并提交对象(v4'v5'),从而改写 dev 的提交历史,使它成为 master 分支的直接下游,如下图: file 现在,就可以回到master分支进行快速合并Fast-forward了,因为master分支和dev分支在一条线上:

    $ git checkout master
    $ git merge dev
    

    file 现在的 v5' 对应的快照,其实和普通的三方合并,即上个例子中的 v8 对应的快照内容一模一样。虽然最后整合得到的结果没有任何区别,但变基能产生一个更为整洁的提交历史。如果视察一个变基过的分支的历史记录,看起来会更清楚:仿佛所有修改都是在一根线上先后进行的,尽管实际上它们原本是同时并行发生的。

    总结

    1. Git保存文件的完整内容,不保存差量变化。
    2. Git以储键值对(key-value)的方式保存文件。
    3. 每一个文件,相同文件的不同版本,都有一个唯一的40位的 SHA-1 校验和与之对应。
    4. SHA-1 校验和是文件的指针,Git依靠它来区分文件。
    5. 每一个文件都会在Git的版本库里生成blob对象来保存。
    6. 对于没有变化的文件,Git只会保留上一个版本的指针。
    7. Git实际上是通过维持复杂的文件树来实现版本控制的。
    8. 使用Git的工作流程基本就是就是文件在三个工作区域之间的流动。
    9. 应该大量使用分支进行团队协作。
    10. 分支只是对提交对象的一个引用。
  • 一步一步教你部署自己的 Laravel 应用程序到服务器

    在部署自己的博客到 LEMP 环境的时候,遇到了一些小挫折,现在把经验分享出来,让大家少走弯路。包括Php7.1安装与下载,SSL证书申请与配置,Mysql升级到5.7,Nginx服务器的简单配置。Let's start.

    注:本教程环境:Ubuntu 14.04.4 LTS (GNU/Linux 3.13.0-86-generic x86_64)

    安装 Php 7.1

    首先用SSH连接到你的服务器,Windows 用户建议使用 SecureCRT 来连接,这是一个收费软件,请自行搜索 破解版

    1. 准备

    安装 Php7.1 之前,要先安装language-pack-en-base这个包,运行:

    sudo apt-get update
    sudo apt-get install -y language-pack-en-base
    

    这个包是为了解决系统不同语言之间可能发生的冲突,安装之后可以减少许多因语言编码带来的问题。其中-y参数表明直接安装,无需确认。

    安装完成之后,运行:

    locale-gen en_US.UTF-8
    

    设定语言编码为UTF-8

    接下来安装Git,Vim,运行:

    sudo apt-get install git vim
    

    Git 是必备的,你可以很方便的使用Git来拉取远程仓库的代码,vim是一款强大的编辑器,可以帮助你修改一些配置文件,如.env文件,如果的你的服务器已经安装了vim编辑器,可以忽略。

    进入正题,安装Php7.1,本教程采用ppa方式安装php7.1,运行:

    sudo apt-get install software-properties-common
    

    software-properties-commonadd-apt-repository所依赖的包,安装成功后,运行:

    sudo LC_ALL=en_US.UTF-8 add-apt-repository ppa:ondrej/php
    

    来添加php7的ppa,注意LC_ALL=en_US.UTF-8参数告诉我们系统语言为UTF-8,如果没有,可能会出现错误,如阿里云的服务器。

    安装完成之后,运行sudo apt-get update更新安装包,把刚才添加的包拉取下来。 运行apt-cache search php7.1搜索php7.1开头的包检验是否安装成功,输出如下:

    [email protected]:~# apt-cache search php7.1
    php-yaml - YAML-1.1 parser and emitter for PHP
    php-apcu - APC User Cache for PHP
    php-ssh2 - Bindings for the libssh2 library
    php-igbinary - igbinary PHP serializer
    php-mailparse - Email message manipulation for PHP
    php-libsodium - PHP wrapper for the Sodium cryptographic library
    php-propro - propro module for PHP
    php-raphf - raphf module for PHP
    
    ....................省略...........................
    

    2. 安装:

    安装php7.1:

    sudo apt-get -y install php7.1
    

    安装成功后运行php -v查看是否安装成功,成功的话会输出类似如下信息:

    PHP 7.1.0beta2 (cli) ( NTS )
    Copyright (c) 1997-2016 The PHP Group
    Zend Engine v3.1.0-dev, Copyright (c) 1998-2016 Zend Technologies
        with Zend OPcache v7.1.0beta2, Copyright (c) 1999-2016, by Zend Technologies
    

    安装php7.1-mysql,这是 Php7.1 与 mysql 的通信模块:

    sudo apt-get -y install php7.1-mysql
    

    安装 fpm,这是Nginx 用来解析php文件的:

    sudo apt-get install php7.1-fpm
    

    安装其他必备模块:

    apt-get install php7.1-curl php7.1-xml php7.1-mcrypt php7.1-json php7.1-gd php7.1-mbstring
    

    至此与php相关的模块安装安装完成。

    安装Mysql

    直接安装Mysql5.7吧,5.7 可以说是里程碑式的版本,提高了性能,并增加了很多新的特性。特别是新增加的json字段,用过之后你会爱上她的!!

    MySQL 开发团队于 9.12 日宣布 MySQL 8.0.0 开发里程碑版本(DMR)发布!但是目前 8.0.0 还是开发版本,如果你希望体验和测试最新特性,可以从 http://dev.mysql.com/downloads/mysql/ 下载各个平台的安装包。不过,MySQL 软件包是越来越大了,Linux 平台上的二进制打包后就将近有 1 GB。如果在产品环境中使用,在 8.0 没有进入稳定版本之前,请继续使用 5.7 系列,当前最新的版本是 5.7.15 GA 版本——这只有 600 M 多。

    1. 下载.deb包到你的服务器:

    wget http://dev.mysql.com/get/mysql-apt-config_0.5.3-1_all.deb
    

    2. 然后使用dpkg命令添加Mysql的源:

    sudo dpkg -i mysql-apt-config_0.5.3-1_all.deb
    

    注意在添加源的时候,会叫你选择安装 MySQL 哪个应用,这里选择 Server 即可,再选择 MySQL 5.7 后又会回到选择应用的那个界面,此时选择 Apply 即可。

    3. 安装

    sudo apt-get update
    sudo apt-get install mysql-server
    

    安装完成之后运行mysql -V查看版本:

    [email protected]:~# mysql -V
    mysql  Ver 14.14 Distrib 5.7.15, for Linux (x86_64) using  EditLine wrapper
    

    4. 注意

    如果你已经通过 ppa 的方式安装了 MySQL 5.6,首先得去掉这个源。

    sudo apt-add-repository --remove ppa:ondrej/mysql-5.6
    # 如果没有 apt-add-repository 先安装上
    # sudo apt-get install software-properties-common
    

    然后其它和上面一样,但最后要运行sudo mysql_upgrade -u root -p 升级数据库,运行sudo service mysql restart重启数据库,这样你的数据会完好无缺(不出意外的话)

    安装Nginx

    简单,运行:

    sudo apt-get -y install nginx
    

    即可,然后运行curl localhost查看是否运行成功。你也可以直接访问你的IP地址,如下:

    file

    配置

    1. 配置php:

    sudo vim /etc/php/7.1/fpm/php.ini
    

    输入/fix_pathinfo搜索,将cgi.fix_pathinfo=1改为cgi.fix_pathinfo=0

    file

    Why ? 假设有如下的 URL:http://phpvim.net/foo.jpg,当访问 http://phpvim.net/foo.jpg/a.php 时,foo.jpg 将会被执行,如果 foo.jpg 是一个普通文件,那么 foo.jpg 的内容会被直接显示出来,但是如果把一段 php 代码保存为 foo.jpg,那么问题就来了,这段代码就会被直接执行。这对一个 Web 应用来说,所造成的后果无疑是毁灭性的。具体参看:http://www.cnblogs.com/buffer/archive/2011/07/24/2115552.html

    2. 编辑fpm的配置文件:

    运行:

    sudo vim /etc/php/7.1/fpm/pool.d/www.conf
    

    找到listen = /run/php/php7.1-fpm.sock修改为listen = /var/run/php/php7.1-fpm.sock。当然,你也可以不修改,但必须前后一致,后面会用到这个配置。

    file

    3. 配置Nginx:

    运行:

    sudo vim /etc/nginx/sites-available/default
    

    主要是配置server这一部分,最终配置如下:

    
    server {
            listen 80 default_server;
            listen [::]:80 default_server ipv6only=on;
    
            root /var/www/your-project-name/public;
            index index.php index.html index.htm;
    
            # Make site accessible from http://localhost/
            server_name lufficc.com www.lufficc.com;
    
            location / {
                    # First attempt to serve request as file, then
                    # as directory, then fall back to displaying a 404.
                    try_files $uri $uri/ /index.php?$query_string;
                    # Uncomment to enable naxsi on this location
                    # include /etc/nginx/naxsi.rules
            }
    				
            location ~ \.php$ {
                    try_files $uri /index.php =404;
                    fastcgi_split_path_info ^(.+\.php)(/.+)$;
                    fastcgi_pass unix:/var/run/php/php7.1-fpm.sock;
                    fastcgi_index index.php;
                    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
                    include fastcgi_params;
            }
    }
    

    解释:

    • root:是你的项目的public目录,也就是网站的入口
    • index:添加了,index.php,告诉Nginx先解析index.php文件
    • server_name:你的域名,没有的话填写localhost
    • location / try_files修改为了try_files $uri $uri/ /index.php?$query_string;
    • ` location ~ .php$部分告诉Nginx怎么解析Php,原封不动复制即可,**但注意:fastcgi_pass unix:/var/run/php/php7.1-fpm.sock;`的目录要和fpm的配置文件中的listen一致**。

    4. 创建网站目录,Laravel项目:

    如果你还没有/var/www目录,运行mkdir /var/www,然后将Nginx的用户名和用户组www-data分配给它:

    sudo chown -R www-data:www-data 
    

    然后进入到/var/www/目录,cd /var/www/。这个www目录就是放置你的网站文件的目录(可以多个)。

    下面以laravel-blog为例,先clone我的laravel-blog,在/var/www/目录下运行:

    git clone https://github.com/lufficc/laravel-blog.git
    

    这会生成在/var/www/目录下生成laravel-blog目录,注意此时上面的Nginx配置的root要和这个一致,当前配置应该是root /var/www/laravel-blog/public;,而不再是root /var/www/your-project-name/public;

    然后composer update,配置.env,数据库连接,没有安装Redis的话安装Redis:apt-get install redis-server

    再次给予目录权限,运行(在/var/www/laravel-blog下面):

    sudo chmod -R 775 storage/
    sudo chown -R www-data:www-data /var/www/laravel-blog
    

    运行ll,结果如下: file

    最后!!重启nginx,fpm,访问你的ip地址,不出意外,安装成功,部署完成!

    sudo service nginx restart
    sudo service php7.1-fpm restart
    

    小结

    啰嗦了那么长,其实实际配置起来很简单,把下面的命令复制,依次运行即可完成大部分的配置,然后再简单修改一下配置文件即可:

    
    # 安装php7.1
    sudo apt-get update
    sudo apt-get install -y language-pack-en-base
    locale-gen en_US.UTF-8
    
    sudo apt-get install software-properties-common
    sudo LC_ALL=en_US.UTF-8 add-apt-repository ppa:ondrej/php
    sudo apt-get update
    
    sudo apt-get -y install php7.1
    sudo apt-get -y install php7.1-mysql
    sudo apt-get install php7.1-fpm
    
    apt-get install php7.1-curl php7.1-xml php7.1-mcrypt php7.1-json php7.1-gd php7.1-mbstring
    
    # 安装mysql5.7
    wget http://dev.mysql.com/get/mysql-apt-config_0.5.3-1_all.deb
    sudo dpkg -i mysql-apt-config_0.5.3-1_all.deb
    sudo apt-get update
    sudo apt-get install mysql-server
    
    # 安装nginx
    sudo apt-get -y install nginx
    

    Https(对这个没要求的可以不看

    如果你熟悉Let’s Encrypt,可以跳过了,本教程使用的是免费的,配置更简单的startssl。

    去官网注册:https://www.startssl.com/, startssl免费还是很诱惑人的哈: file

    1. 验证

    登录后之后进去主页,点击Validations Wizard,选择第一项Domain Validation (for SSL certificate),点击Continue验证你是否是域名的主人:

    file

    file

    然后点击将验证信息发送到你的邮箱,输入验证码继续

    file

    验证成功,startssl认为你就是域名的所有者,此时进行下一步操作: file

    2. 选择域名

    点击Certificates Wizard,选择第一项:

    file

    输入所有你想要ssl的域名,例如static子域名,blog子域名:

    file

    3.生成 CSR (Certificate Signing Request ) key:

    复制openssl req -newkey rsa:2048 -keyout yourname.key -out yourname.csr到你的服务器命令行,:

    file

    填写你的信息:

    file

    然后会生成两个文件:

    file

    运行cat lufficc.csr复制lufficc.csr的内容到上面的输入框,点击Submit:

    file

    此时你的证书已经生成功,点击下载即可下载到本地。

    file

    下载完成后解压,会有四个文件,对应不同软件,这里我们只需要Nginx的就行:

    file

    file

    4. 解密私钥

    点击startssl的Tool Box下的Decrypt Private Key,输入lufficc.key(刚才命令行生成的.key结尾的文件)文件内容和你刚才设置的密码(刚才命令行设置的密码),点击解密

    file

    复制解压后的内容,运行vim lufficc_de.key生成一个新文件,复制进去解密后的key,保存。 然后在新建一个目录/etc/nginx/ssl,将刚才的文件放进去,便于统一管理: file

    其中红色框框的是要用到文件。

    5. 配置Nginx

    运行sudo vim /etc/nginx/sites-available/default,翻到最下面注释掉的HTTPS server,最终配置如下:

    server {
            listen 443;
            server_name lufficc.com www.lufficc.com;
    
            root /var/www/your-project-name/public;
            index index.php index.html index.htm;
    
            ssl on;
            ssl_certificate /etc/nginx/ssl/1_lufficc.com_bundle.crt;
            ssl_certificate_key /etc/nginx/ssl/lufficc_de.key;
    
            ssl_protocols              TLSv1 TLSv1.1 TLSv1.2;
            ssl_prefer_server_ciphers  on;
            ssl_session_cache    shared:SSL:10m;
            ssl_session_timeout  24h;
    
    
            location / {
                    try_files $uri $uri/ /index.php?$query_string;
            }
    
            location ~ \.php$ {
                    try_files $uri /index.php =404;
                    fastcgi_split_path_info ^(.+\.php)(/.+)$;
                    fastcgi_pass unix:/var/run/php/php7.1-fpm.sock;
                    fastcgi_index index.php;
                    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
                    include fastcgi_params;
            }
    

    和http配置基本一样,区别是

            ssl on;
            ssl_certificate /etc/nginx/ssl/1_lufficc.com_bundle.crt;
            ssl_certificate_key /etc/nginx/ssl/lufficc_de.key;
    

    要和你的目录一致。接下来在80端口的http server中添加跳转,return 301 https://lufficc.com;这样当别人用http 协议访问你的站点时,会自动调转到https,如下:

    
    server {
            listen 80 default_server;
            listen [::]:80 default_server ipv6only=on;
    
            root /var/www/laravel/public;
            index index.php index.html index.htm;
    
            # Make site accessible from http://localhost/
            server_name lufficc.com www.lufficc.com *.lufficc.com;
            return      301 https://lufficc.com;
    
            location / {
                    # First attempt to serve request as file, then
                    # as directory, then fall back to displaying a 404.
                    try_files $uri $uri/ /index.php?$query_string;
    								
     ....................省略...........................
    

    最后,测试一行nginx的配置是否正确,运行sudo service nginx configtest

    file

    重新加载并重启nginx:

    sudo service nginx reload
    sudo service nginx restart
    

    测试通过,访问你的站点,看有没有加上那把别致的小绿锁?

    file

    至此,教程结束。最终效果如本站所示。

    结语

    写了那么长的教程,看起来很负责,但只是我啰嗦而已,你把上面的命令一敲,软件安装都很简单,在进行一些简单的配置即可,如果你熟悉整个流程的话,其实没有那么复杂。有问题欢迎在评论指出。

    Have a nice day!

    参考:

    • 感谢laravist系列教程以及jellybool对laravel在中国发展的贡献: https://laravist.com/series/deploy-laravel-app-on-vps

    感谢下列教程给予的启发和帮助:

    • php7.1: http://www.jianshu.com/p/9d4565afa756

    • 配置: http://www.cnblogs.com/buffer/archive/2011/07/24/2115552.html

    • MySQL: https://iyaozhen.com/ubuntu-upgrade-mysql-to-5-7.html https://www.digitalocean.com/community/tutorials/how-to-install-mysql-on-ubuntu-14-04

    • startssl: https://www.freehao123.com/startssl-ssl-apache-ngnix/

  • 一步一步教你上传自己的 Library 到 JCenter

    我们在Android开发过程中一定会用到别人的库,比如squareup公司的OKHttp:

    compile 'com.squareup.okhttp3:okhttp:3.4.1'
    

    这样我们版本更新的时候只需要更改一下版本号就行,而不用去下载jar包,给开发带来了极大的便利,但如果我们自己想上传lib供其他开发者使用呢?那么此教程会带着你一步一步发布自己的library。Let’s go!

    确定要上传的Library

    如果你有Library可以忽略此步骤,没有的话添加library。在Android Studio中选择File->New->New Module,然后选择一个Library,新建一个Library。这里以新建DemoLibrary为例子。(注意这里的Library需要后面的Package的名字一致)。 新建一个Library 新建一个Library

    现在项目的结构如下图,接下来就是添加必要的Jcenter的依赖,为上传做准备。 项目的结构


    注册账号

    首先去bintray官网注册账号,注册完成后验证邮箱,然后登陆进入首页点击View All,选择Maven仓库,新建一个Package,填写Package名字(注意Package需要和你的Library的名字一致)

    注意点击你的头像->Your Profile->Edit->Api key,这个先记下来,后面上传要用到。 Api key

    新建一个Package 新建一个Package 新建一个Package


    添加依赖

    在整个工程的build.gradle文件中添加classpath 'com.novoda:bintray-release:0.3.4',注意是整个工程的build.gradle

    添加依赖

    接着是在你自己Library(这里是DemoLibrary)的build.gradle的文件中配置自己的信息,复制下面的脚本,改成你自己的信息即可

    apply plugin: 'com.android.library'
    apply plugin: 'com.novoda.bintray-release'
    
    publish {
        userOrg = 'lufficc' //你的用户名
        groupId = 'com.lufficc' //你的唯一的groupId,对应com.squareup.okhttp3:okhttp:3.4.1中的com.squareup.okhttp3
        artifactId = 'DemoLibrary' //你的library的名字,对应com.squareup.okhttp3:okhttp:3.4.1中的okhttp
        publishVersion = '0.0.1' //版本号
        desc = 'This is a demo library to teach how to publish you own library to jcenter with android studio.'
        website = 'http://lufficc.com/' //建议填写github地址,不过不影响,这里做演示填的自己的网址
     
        bintrayUser = 'lufficc' //你的用户名
        bintrayKey = 'Your api key' //在你的账户里面查找
    }
    

    经过上面的配置,上传成功后那么别人引用你的library的代码就为compile 'com.lufficc:DemoLibrary:0.0.1'

    你自己Library的build.gradle的文件中配置自己的信息


    上传

    经过上面的配置,现在就可以传了,上传之前记得Sync一下Project,然后打开命令行,输入,回车:

    gradlew clean build bintrayUpload -PdryRun=false
    

    然后等待几分钟,期间会联网下载依赖的库,最后如果没有问题,会显示BUILD SUCCESSFUL信息,然后去官网查看刚才建的Package,会发现多了你刚才上传的版本号。

    BUILD SUCCESSFUL 上传成功的Package

    点进去可以看到有三种引用方式:

    但是到这里还无法让别人也能引用,目前只是你自己的私人库。下面是添加到Jcenter,非常简单。


    添加到Jcenter

    在上面的页面中点击Add To JCenter,然后随便填写一下comments,点击send,然后工作人员会审核和,你只需等待几个小时,然后会有站内消息提示你已经发布发到Jcenter,这样别人也可以引用你的Library,有没有很自豪的感觉!


    更新版本号

    这个非常简单,当你的Libraryd代码更改后,只需要更改一下上面的配置里面的publishVersion,运行gradlew clean build bintrayUpload -PdryRun=false,就可以更新版本号了。这样,整个过程就结束了,遇到什么问题欢迎评论提出或者私信我。


    总结

    其实上传没那么复杂

    1. 注册账号
    2. 为自己的Library项目添加依赖,配置信息
    3. 上传,添加到Jcenter
    4. 更新版本号

    常见问题

    1. 如果你的Java doc含有中文导致上传失败,可以尝试在lib的build.gradle添加如下代码:
       allprojects {
       tasks.withType(Javadoc) {
           options{
               encoding "UTF-8"
               charSet 'UTF-8'
               links "http://docs.oracle.com/javase/7/docs/api"
           }
       }
      }
      
    2. 本教程是基于插件novoda/bintray-release的,更多问题可以查看issues或者查看Wiki