Laravel实战

Project

composer create-project laravel/laravel laravel-system-admin && cd laravel-system-admin

vim .env
APP_SALT="salt"

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=db
DB_USERNAME=root
DB_PASSWORD="password"

IDE

composer require --dev barryvdh/laravel-ide-helper

vim app/Providers/AppServiceProvider.php
<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
    }

    public function register()
    {
        if ($this->app->environment() !== 'production') {
            $this->app->register(\Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider::class);
        }
    }
}
php artisan ide-helper:generate

Hasher

mkdir -p app/Services/Auth

vim app/Services/Auth/PasswordHasher.php
<?php

namespace App\Services\Auth;

use Illuminate\Hashing\BcryptHasher;

class PasswordHasher extends BcryptHasher
{
    protected $salt;

    public function __construct()
    {
        parent::__construct();
        $this->salt = env('APP_SALT');
    }

    public function make($value, array $options = [])
    {
        return parent::make(hash('sha256', $value . $this->salt));
    }

    public function check($value, $hashedValue, array $options = [])
    {
        return parent::check(hash('sha256', $value . $this->salt), $hashedValue);
    }
}

Provider

vim app/Providers/AuthServiceProvider.php
<?php

namespace App\Providers;

use App\Services\Auth\PasswordHasher;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Auth;

class AuthServiceProvider extends ServiceProvider
{
    protected $policies = [
        'App\Model' => 'App\Policies\ModelPolicy',
    ];

    public function boot()
    {
        $this->registerPolicies();

        Auth::guard('admin')->getProvider()->setHasher($this->app['passwordHasher']);
    }

    public function register()
    {
        $this->app->singleton('passwordHasher', function () {
            return new PasswordHasher();
        });
    }
}

Config

vim config/auth.php
<?php

return [
    'guards' => [
        'admin' => [
            'driver' => 'session',
            'provider' => 'admins',
        ],
    ],

    'providers' => [
        'admins' => [
            'driver' => 'eloquent',
            'model' => App\Models\Admin::class,
        ],
    ],
];

Model

php artisan make:model Models/Admin

vim app/Models/Admin.php
<?php

namespace App\Models;

use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;

class Admin extends Authenticatable
{
    use Notifiable;

    protected $table = 'admin';

    protected $fillable = [
        'nick_name', 'unique_name', 'password',
    ];

    protected $hidden = [
        'password', 'remember_token',
    ];
}

Route

vim routes/web.php
<?php

Route::get('/', function () {
    return view('welcome');
});

Route::post('/login', function () {
    $credential = [
        'unique_name' => '18020125636',
        'password' => '18020125636'
    ];

    if(! Auth::guard('admin')->attempt($credential)) {
        return [ 'code' => -1 ];
    }

    return [ 'code' => 0 ];
});

CSRF

vim app/Http/Middleware/VerifyCsrfToken.php
<?php

namespace App\Http\Middleware;

use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;

class VerifyCsrfToken extends Middleware
{
    protected $except = [
        'login'
    ];
}
  • 测试
php artisan serve

curl -X POST http://127.0.0.1:8000/login | json
{
  "code": 0
}

Redis

docker run --name laravel-system-admin -p 6379:6379 -d redis
composer require predis/predis

vim .env
SESSION_DRIVER=redis

Route

vim routes/web.php
<?php

Route::post('/login', function () {
    $credential = [
        'unique_name' => '18020125636',
        'password' => '18020125636'
    ];

    if(! Auth::guard('admin')->attempt($credential)) {
        return [ 'code' => -1 ];
    }

    $user = Auth::guard('admin')->user();

    return [
        'code' => 0,
        'data' => $user,
    ];
});

Route::group(['middleware' => 'auth:admin'], function () {
    Route::get('/user', function () {
        return 'user';
    });
});
  • 测试
php artisan serve
curl localhost:8000 # Page not found

curl localhost:8000/user # AuthenticationException 

Exception

vim app/Exceptions/Handler.php
<?php

namespace App\Exceptions;

use Exception;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

class Handler extends ExceptionHandler
{
    protected $dontReport = [
    ];

    protected $dontFlash = [
        'password',
        'password_confirmation',
    ];

    public function report(Exception $exception)
    {
        parent::report($exception);
    }

    public function render($request, Exception $exception)
    {
        if ($exception instanceof NotFoundHttpException) {
            return response()->json(['message' => $exception->getMessage()], 404);
        } else if ($exception instanceof AuthenticationException) {
            return response()->json(['message' => $exception->getMessage()], 401);
        }

        return parent::render($request, $exception);
    }
}
  • 测试
php artisan serve
curl localhost:8000 | json
{
  "message": ""
}
curl -c cookies -X POST localhost:8000/login | json 
{
  "code": 0,
  "data": {
    "id": 6,
    "created_at": "2018-01-15 09:34:16",
    "updated_at": "2018-07-23 17:55:27",
    "nick_name": "王世新",
    "unique_name": "18020125636",
    "is_active": 1
  }
}
curl -b cookies localhost:8000/user # user

Controller

php artisan make:controller AuthorizationsController

vim app/Http/Controllers/AuthorizationsController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Support\Facades\Auth;

class AuthorizationsController extends Controller
{
    public function store() {
        $credential = [
            'unique_name' => '18020125636',
            'password' => '18020125636'
        ];

        if(! Auth::guard('admin')->attempt($credential)) {
            return [ 'code' => -1 ];
        }

        $user = Auth::guard('admin')->user();

        return [
            'code' => 0,
            'data' => $user,
        ];
    }
}
vim routes/web.php
<?php

Route::post('/login', 'AuthorizationsController@store')->name('auth.login');;

Route::group(['middleware' => 'auth:admin'], function () {
    Route::get('/user', function () {
        return 'user';
    });
});

Request

php artisan make:request AuthorizationRequest

vim app/Http/Requests/AuthorizationRequest.php
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class AuthorizationRequest extends FormRequest
{
    public function authorize()
    {
        return true;
    }

    public function rules()
    {
        return [
            'unique_name' => 'required|string',
            'password' => 'required|string|min:6',
        ];
    }
}
vim app/Http/Controllers/AuthorizationsController.php
<?php

namespace App\Http\Controllers;

use App\Http\Requests\AuthorizationRequest;
use Illuminate\Support\Facades\Auth;

class AuthorizationsController extends Controller
{
    public function store(AuthorizationRequest $request) {
        $credentials['unique_name'] = $request->unique_name;
        $credentials['password'] = $request->password;

        if(! Auth::guard('admin')->attempt($credentials)) {
            return [ 'code' => -1 ];
        }

        $user = Auth::guard('admin')->user();

        return [
            'code' => 0,
            'data' => $user,
        ];
    }
}
vim app/Exceptions/Handler.php
<?php

namespace App\Exceptions;

use Exception;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

class Handler extends ExceptionHandler
{
    protected $dontReport = [
    ];

    protected $dontFlash = [
        'password',
        'password_confirmation',
    ];

    public function report(Exception $exception)
    {
        parent::report($exception);
    }

    public function render($request, Exception $exception)
    {
        if ($exception instanceof NotFoundHttpException) {
            return response()->json(['message' => $exception->getMessage()], 404);
        } else if ($exception instanceof AuthenticationException) {
            return response()->json(['message' => $exception->getMessage()], 401);
        } else if ($exception instanceof ValidationException) {
            return response()->json([
                'status_code' => 400,
                'message' => $exception->getMessage(),
            ], 200);
        }

        return parent::render($request, $exception);
    }
}

Resource

php artisan make:resource JsonModel

vim app/Http/Resources/JsonModel.php
<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class JsonModel extends JsonResource
{
    protected $result;

    const KEY_CACHE_HEADER = 'XETag';
    const KEY_CODE = 'XCmdrCode';
    const KEY_MESSAGE = 'XCmdrMessage';
    const KEY_RESULT = 'XCmdrResult';

    public function __construct($variables, $code = JsonCode::SUCCESS, $message = null)
    {
        $data = [self::KEY_CACHE_HEADER => ''];
        $data [self::KEY_CODE] = $code;
        $data [self::KEY_MESSAGE] = $message ?? JsonCode::getCodeMessage($code);
        $data [self::KEY_RESULT] = $variables;

        parent::__construct($data);

        $this->result = $data;
    }

    public function toArray($request)
    {
        return $this->result;
    }
}
vim app/Http/Resources/JsonCode.php
<?php

namespace App\Http\Resources;

class JsonCode
{
    const SUCCESS = 0;

    // HTTP 请求方法不正确
    const HTTP_METHOD_INVALID = 100;

    // HTTP请求必须为GET
    const HTTP_METHOD_MUST_BE_GET = 110;

    // HTTP请求必须为POST
    const HTTP_METHOD_MUST_BE_POST = 120;

    // HTTP请求必须为DELETE
    const HTTP_METHOD_MUST_BE_DELETE = 130;

    // 没有权限
    const PERMISSION_INVALID = 200;

    // 没有指挥官权限
    const TENANT_PERMISSION_INVALID = 201;

    // 未登录
    const LOGIN_REQUIRED = 210;

    // 数据不正确
    const DATA_INVALID = 300;

    // 不被识别的数据类型
    const DATA_UNRECOGNIZED = 310;

    // 数据格式不正确
    const DATA_FORMAT_INVALID = 311;

    // 表单数据不正确
    const DATA_FORM_INVALID = 320;

    // 数据已存在
    /** @deprecated use RESOURCE_NOT_EXIST instead */
    const DATA_EXIST = 330;

    // 电话号码已存在
    const DATA_PHONE_EXIST = 331;

    // 数据不存在
    const DATA_NOT_EXIST = 340;

    // 电话号码不存在
    const DATA_PHONE_NOT_EXIST = 341;

    // 参数错误
    const DATA_FIELD_INVALID = 350;

    // 验证码不正确
    const DATA_VERIFY_CODE_INVALID = 351;

    // 验证码失效
    const DATA_VERIFY_CODE_FAILED = 352;

    // 验证码发送失败
    const DATA_VERIFY_CODE_SEND_FAILED = 353;

    // 获取用户信息失败
    const AUTH_FAILED = 400;

    // 用户身份异常
    const AUTH_IDENTITY_ABNORMAL = 410;

    // 用户未激活
    const AUTH_IDENTITY_NOT_ACTIVE = 411;

    // 登录失败
    const LOGIN_FAILED = 420;

    // 微信用户不存在
    const WEUSER_NOT_EXIST = 421;

    // 平台账号不存在
    const PASSPORT_NOT_EXIST = 422;

    // 没有可用的帐户
    const USERS_NOT_EXIST = 423;

    // 租户正在审核中
    const TENANT_CHECKING = 424;

    // 客户端类型错误
    const CLIENT_INVALID = 500;

    // 操作失败
    const OPERATE_FAILED = 600;

    // 操作次数已达上限
    const REACH_THE_MAX = 610;

    // 资源无效
    const RESOURCE_INVALID = 700;

    // 资源不存在
    const RESOURCE_NOT_EXIST = 710;

    // 租户不存在
    const TENANT_NOT_EXIST = 711;

    // 资源已存在
    const RESOURCE_EXIST = 720;

    // 资源未改变
    const RESOURCE_NOT_MODIFIED = 730;

    // 资源已改变
    const RESOURCE_MODIFIED = 740;

    // API无效
    const API_INVALID = 800;

    private static $json_code_messages = [
        self::SUCCESS => '成功',
        self::HTTP_METHOD_INVALID => '错误的请求',
        self::HTTP_METHOD_MUST_BE_GET => '必须为GET请求',
        self::HTTP_METHOD_MUST_BE_POST => '必须为POST请求',
        self::HTTP_METHOD_MUST_BE_DELETE => '必须为DELETE请求',
        self::PERMISSION_INVALID=> '没有权限',
        self::TENANT_PERMISSION_INVALID => '没有指挥官权限',
        self::LOGIN_REQUIRED => '未登录',
        self::DATA_INVALID => '数据不正确',
        self::DATA_UNRECOGNIZED => '不被识别的数据类型',
        self::DATA_FORMAT_INVALID => '数据格式不正确',
        self::DATA_FORM_INVALID => '表单数据不正确',
        self::DATA_EXIST => '数据已存在',
        self::DATA_PHONE_EXIST => '电话号码已存在',
        self::DATA_NOT_EXIST => '数据不存在',
        self::DATA_PHONE_NOT_EXIST => '电话号码不存在',
        self::DATA_FIELD_INVALID => '参数错误',
        self::DATA_VERIFY_CODE_INVALID => '验证码不正确',
        self::DATA_VERIFY_CODE_FAILED => '验证码失效',
        self::AUTH_FAILED => '获取用户信息失败',
        self::AUTH_IDENTITY_ABNORMAL => '用户身份异常',
        self::AUTH_IDENTITY_NOT_ACTIVE => '用户未激活',
        self::LOGIN_FAILED => '登录失败',
        self::WEUSER_NOT_EXIST => '微信用户不存在',
        self::PASSPORT_NOT_EXIST => '平台账号不存在',
        self::USERS_NOT_EXIST => '没有可用的帐户',
        self::TENANT_CHECKING => '租户正在审核中',
        self::CLIENT_INVALID => '客户端类型错误',
        self::OPERATE_FAILED => '操作失败',
        self::REACH_THE_MAX => '操作次数已达上限',
        self::RESOURCE_INVALID => '资源无效',
        self::RESOURCE_NOT_EXIST => '资源不存在',
        self::TENANT_NOT_EXIST => '租户不存在',
        self::RESOURCE_EXIST => '资源已存在',
        self::RESOURCE_NOT_MODIFIED => '资源未改变',
        self::RESOURCE_MODIFIED => '资源已改变',
        self::API_INVALID => 'API无效',
        self::DATA_VERIFY_CODE_SEND_FAILED => '验证码发送失败'
    ];

    public static function getCodeMessage($code)
    {
        return isset($code, self::$json_code_messages) ? self::$json_code_messages [$code] : '';
    }
}
vim app/Providers/AppServiceProvider.php
<?php

namespace App\Providers;

use Illuminate\Http\Resources\Json\Resource;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
        Resource::withoutWrapping();
    }

    public function register()
    {
        if ($this->app->environment() !== 'production') {
            $this->app->register(\Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider::class);
        }
    }
}
vim app/Http/Controllers/AuthorizationsController.php
<?php

namespace App\Http\Controllers;

use App\Http\Requests\AuthorizationRequest;
use App\Http\Resources\JsonModel;
use Illuminate\Support\Facades\Auth;

class AuthorizationsController extends Controller
{
    public function store(AuthorizationRequest $request) {
        $credentials['unique_name'] = $request->unique_name;
        $credentials['password'] = $request->password;

        if(! Auth::guard('admin')->attempt($credentials)) {
            return [ 'code' => -1 ];
        }

        $user = Auth::guard('admin')->user();

        return new JsonModel($user);
    }
}
  • 测试
php artisan serve
curl -X POST \
  http://127.0.0.1:8000/login \
  -H 'content-type: application/json' \
  -d '{
    "unique_name": "18020125636",
    "password": "18020125636"
}' | json
{
  "XETag": "",
  "XCmdrCode": 0,
  "XCmdrMessage": "成功",
  "XCmdrResult": {
    "id": 6,
    "created_at": "2018-01-15 09:34:16",
    "updated_at": "2018-07-23 17:55:27",
    "nick_name": "王世新",
    "unique_name": "18020125636",
    "is_active": 1
  }
}

Controller

php artisan make:controller MachinesController

vim app/Http/Controllers/MachinesController.php
<?php

namespace App\Http\Controllers;

use App\Http\Resources\JsonModel;

class MachinesController extends Controller
{
    public function index() {
        return new JsonModel([]);
    }
}

Route

vim routes/web.php
<?php

Route::post('/login', 'AuthorizationsController@store')->name('auth.login');

Route::group(['middleware' => 'auth:admin'], function () {
    Route::get('/user', function () {
        return 'user';
    });
});

Route::get('/machines', 'MachinesController@index')->name('machines.index');

Query

vim app/Http/Controllers/MachinesController.php
<?php

namespace App\Http\Controllers;

use App\Http\Resources\JsonModel;
use Illuminate\Support\Facades\DB;
use Illuminate\Http\Request;

class MachinesController extends Controller
{
    public function index(Request $request) {
        $online = $request->query('online');
        $count_per_page = $request->query('count_per_page', 10);

        $qb = DB::table('machine AS m')
                ->leftJoin('machine_online_state AS s', 's.machine_id', '=', 'm.id');
        if ($online) {
            $qb->where('s.state', 'online');
        }
        $machines = $qb->where('m.deleted', 0)->paginate($count_per_page);
        return new JsonModel([ 'data_list' => $machines ]);
    }
}

Model

php artisan make:model Models/Category

vim app/Models/Category.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Category extends Model
{
    protected $table = 'category';
}
php artisan make:model Models/Machine

vim app/Models/Machine.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Machine extends Model
{
    protected $table = 'machine';

    public function category()
    {
        return $this->hasOne('App\Models\Category', 'id', 'category_id');
    }
}
vim app/Http/Controllers/MachinesController.php
<?php

namespace App\Http\Controllers;

use App\Http\Resources\JsonModel;
use App\Models\Machine;
use Illuminate\Http\Request;

class MachinesController extends Controller
{
    public function index(Request $request) {
        $online = $request->query('online');
        $count_per_page = $request->query('count_per_page', 10);

        $qb = Machine::leftJoin('machine_online_state AS s', 's.machine_id', '=', 'machine.id');
        if ($online) {
            $qb->where('s.state', 'online');
        }
        $qb = $qb->where('machine.deleted', 0);
        $paginator = $qb->simplePaginate($count_per_page);

        return new JsonModel([ 'data_list' => $paginator ]);
    }
}

Paginator

vim app/Http/Resources/PaginatorJsonModel.php
<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\ResourceCollection;
use Illuminate\Pagination\LengthAwarePaginator;

class PaginatorJsonModel extends JsonModel
{
    const KEY_DATA_LIST = 'data_list';
    const KEY_TOTAL_ITEM_COUNT = 'total_item_count';
    const KEY_TOTAL_PAGE_COUNT = 'total_page_count';

    public function __construct(ResourceCollection $variables)
    {
        $resource = $variables->resource;
        if (! $resource instanceof LengthAwarePaginator) {
            throw new \InvalidArgumentException("class_name must be instance of ".LengthAwarePaginator::class."!");
        }

        $data = [
            self::KEY_DATA_LIST => $variables,
            self::KEY_TOTAL_ITEM_COUNT => $resource->count(),
            self::KEY_TOTAL_PAGE_COUNT => $resource->lastPage(),
        ];

        parent::__construct($data);
    }
}
php artisan make:resource MachinesResource

vim app/Http/Resources/MachinesResource.php
<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class MachinesResource extends JsonResource
{
    public function toArray($request)
    {
        $machine = $this;
        $category = $machine->category;

        return [
            'id' => $machine->id,
            'machine_name' => $machine->machine_name,
            'category' => [
                'id' => $category->id,
                'category_name' => $category->category_name,
            ],
        ];
    }
}
vim app/Http/Controllers/MachinesController.php
<?php

namespace App\Http\Controllers;

use App\Http\Resources\MachinesResource;
use App\Http\Resources\PaginatorJsonModel;
use App\Models\Machine;
use Illuminate\Http\Request;

class MachinesController extends Controller
{
    public function index(Request $request) {
        $online = $request->query('online');
        $count_per_page = $request->query('count_per_page', 10);

        $qb = Machine::leftJoin('machine_online_state AS s', 's.machine_id', '=', 'machine.id');
        if ($online) {
            $qb->where('s.state', 'online');
        }
        $qb = $qb->where('machine.deleted', 0);
        $paginator = $qb->paginate($count_per_page);

        return new PaginatorJsonModel(MachinesResource::collection($paginator));
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容