laravel 学习笔记

216 阅读4分钟

目录结构

  1. app目录:包含应用程序的核心代码,例如控制器、模型和服务提供者等。
  2. bootstrap目录:包含启动应用程序所需的文件,例如自动生成的类加载器和环境配置文件等。
  3. config目录:包含应用程序的所有配置文件,例如数据库连接、队列设置和应用程序调试等级等。
  4. database目录:包含应用程序的数据库迁移和种子数据。
  5. public目录:包含应用程序的前端资源,例如JavaScript、CSS和图像等。
  6. resources目录:包含应用程序的视图、语言文件和未编译的前端资源。
  7. routes目录:包含应用程序的路由定义文件,指定URL与Controller的对应关系。
  8. storage目录:包含应用程序的缓存、日志和上传的文件等。
  9. tests目录:包含应用程序的自动化测试用例。
  10. vendor目录:包含Laravel的核心框架及其依赖项,通常不需要手动更改。
  11. 参考 www.jianshu.com/p/4185d2778…

执行顺序

  1. Laravel 会首先进行路由匹配,根据路由定义中指定的 URI、HTTP 方法和中间件等信息来匹配合适的路由。
  2. 如果匹配成功,则控制权交给相应的控制器方法进行处理。在控制器方法中,可以通过调用中间件来对请求进行进一步的过滤和处理,确保只有符合条件的请求才能访问控制器方法中定义的业务逻辑。中间件可以对请求进行各种验证、授权、数据转换等操作,从而确保系统的安全性和正确性。
  3. 当某个中间件验证成功后,它会执行 handle 方法,然后将控制权交给下一个中间件或者控制器方法进行处理。在执行 $next($request) 之前,中间件有机会对请求和响应进行修改和处理,从而影响后续的请求流程和响应结果。其中 $next 是一个闭包函数或者可调用对象,它代表下一个中间件或者控制器方法。

在哪里查看路由配置和 URL 重写规则?

  1. routes/web.php 文件:用于定义 Web 路由,即处理 HTTP 请求的路由。
  2. routes/api.php 文件:用于定义 API 路由,即处理 RESTful API 请求的路由。
  3. .htaccess 文件(位于项目根目录):用于定义 Apache Web 服务器的 URL 重写规则。该文件只在使用 Apache 服务器时存在。
  4. nginx.conf 文件(位于项目根目录):用于定义 Nginx Web 服务器的 URL 重写规则。该文件只在使用 Nginx 服务器时存在。

Services

在 Laravel 中,app/Services 目录通常用于存放应用程序的服务类。服务类是一种可重用的代码块,用于封装应用程序中的一些常见功能,例如发送电子邮件、处理文件上传、生成 PDF 等。服务类通常包含一个或多个公共方法,这些方法可以在应用程序的其他部分中重复使用。 服务类通常使用依赖注入来管理它们的依赖关系,在需要时实例化。

发送邮件

1.在 Laravel 中发送邮件可以使用内置的 Mail 类。首先需要在 .env 文件中配置邮件服务商的信息:

MAIL_MAILER=smtp
MAIL_HOST=smtp.qq.com
MAIL_PORT=465
MAIL_USERNAME=你的QQ邮箱账号
MAIL_PASSWORD=你的QQ邮箱授权码
MAIL_ENCRYPTION=ssl
MAIL_FROM_ADDRESS=发件人邮箱地址
MAIL_FROM_NAME=发件人名称

2.控制器方法

use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Cache;
 public function email(Request $request, $id)
    {
        // 使用 pluck() 方法获取 email 字段的值,然后使用 first() 方法获取第一个匹配的结果。
        $email = Test::where('id', $id)->pluck('email')->first();
        $code = rand(1000, 9999);
        // 将数据存储在 Laravel 的缓存系统中
        // 可以在 storage/framework/cache/data 目录中找到缓存文件
        Cache::put('email_verification_code:' . $email, $code, 60);
        // 获取验证码
        //  $cachedCode = Cache::get('email_verification_code:' . $email);

        //使用blade模板发送(引用的模板)
        Mail::send('admin.emails.email', ['code' => $code], function ($message) use ($email) {
            $message->to($email)->subject('水蜜桃的夏天');
        });
        if (Mail::failures()) {
            return ["code" => 0, "msg" => "error"];
        }
        return ["code" => 1, "msg" => "success"];
    }

3.blade模板

<!DOCTYPE html>
<html>
<head>
	<title>邮箱测试模板</title>
</head>
<body>
	<h1>您的验证码是,{{$code}}!</h1>
	<p>请在一分钟内注册,啦啦啦啦啦啦啦!!!!</p>
</body>
</html>

导入EXCEL

  • 视图
<input type="file" id="inputExcel" style="display: none" accept=".xlsx, .xls"  data-url="{{url(strtolower($model).'/import')}}">
<button type="reset" class="layui-btn layui-btn-warm" id="import_csv"><i class="layui-icon layui-icon-upload-circle  layuiadmin-button-btn"></i>导入</button>
  • JS
 // EXCEL导入
    $(document).on('click', '#import_csv', function () {
        $('#inputExcel').click();
    });
    $('#inputExcel').on('change', function (e) {
        console.log(e);
        var file = this.files[0];
        var formData = new FormData();
        formData.append('file', file);
        var url = $(this).data('url'); // 获取 data-url 属性的值
        $.ajax({
            url: url,
            data: formData,
            type: 'POST',
            contentType: false,
            processData: false,
            dataType: 'json',
            cache: false,
            headers: {
                'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
            },
            success: function (res) {
                // 处理上传成功的回调
                history.go(0);
                alert(res.msg);
            },
            error: function (err) {
                // 处理上传失败的回调
                console.error('Upload error:', err);
            }
        });
    })
  • 导入的方法封装在了Services,采取注入的方式来实例调用,这里只演示导入
<?php

namespace App\Services;

use App\Models\Test;
use Illuminate\Http\Request;
use PhpOffice\PhpSpreadsheet\IOFactory;
use Illuminate\Support\Facades\DB;

class TestService
{
     public static function import(Request $request)
    {
        // 判断当前请求是否为POST请求,并且检查是否存在名为file的上传文件。如果条件成立,则继续执行代码;否则返回错误信息
        if ($request->isMethod('post') && $request->hasFile('file')) {
            // 生成一个唯一的文件名,使用当前时间戳和一个随机数进行MD5加密,最后加上后缀名.xlsx。
            $fileName = md5(microtime(true) . mt_rand(1000, 9999)) . '.xlsx';
            // 获取应用程序根目录下的/storage/app/public/uploads目录,并将其赋值给变量$root。
            $root = storage_path('/public/uploads/');
            // 生成一个以当前日期为名称的子目录,用于存放上传的Excel文件。
            $savePath = 'importExcel/' . date('Ymd') . '/';
            // 将上传文件的路径设置为$root与$savePath的拼接结果。
            $path = $root . $savePath;
            // is_dir查上传文件的目录是否存在,如果不存在则创建该目录。
            if (!is_dir($path)) {
                // mkdir($path, 0777, true) 该方法创建名为$path的目录,并赋予最大的权限
                if (false === @mkdir($path, 0777, true) && !is_dir($path)) {
                    throw new \Exception('存储文件夹创建失败:' . $path);
                }
            }
            // 组合出完整的文件路径。
            $filePath = $path . $fileName;
            // 将上传的Excel文件保存到服务器上指定的位置
            if ($request->file('file')->move($path, $fileName)) {
                // 加载Excel文件
                $spreadsheet = IOFactory::load($filePath);
                // 获取第一个工作表
                $worksheet = $spreadsheet->getActiveSheet()->toArray(null, true, true, true);
                // 获取A1单元格的值
                // $value = $worksheet->getCell('A1')->getValue();
                // 处理Excel文件内容并保存到数据库中
                DB::beginTransaction();
                try {
                    foreach ($worksheet as $key => $value) {
                        //  跳过表头
                        if ($key == 1) continue;
                        // 跳过空行
                        if (empty($value['A'])) continue;
                        $model = new Test;
                        $model['name'] = $value['A'];
                        // 当参数为false时,会禁用数据验证,直接保存数据到数据库中。而当参数为true时,会强制进行数据验证,
                        $model->saveOrFail([
                            'timestamps' => false,
                        ]);
                    }
                    DB::commit();
                    return response()->json(['code' => 0, 'msg' => '文件上传成功']);
                } catch (\Exception $e) {
                    DB::rollBack();
                    return response()->json(['code' => 1, 'msg' => $e->getMessage()]);
                }
            } else {
                return response()->json(['code' => 1, 'msg' => '文件上传失败']);
            }
        } else {
            return response()->json(['code' => $request, 'msg' => 'Error!']);
        }
    }
}
  • 调用import方法
<?php

namespace App\Http\Controllers\Admin;

use App\Helpers\Common;
use App\Models\Test;
use App\Services\TestService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;

class TestController  extends BaseController
{
    // 在 `TestController` 中添加一个构造函数,并在其中注入 `TestService` 实例。
    protected $testService;
    public function __construct(TestService $testService)
    {
        $this->testService = $testService;
    }
    
    // 导入EXCEL
    public function import(Request $request)
    {
        return $this->testService->import($request);
    }
}

为什么使用中间件跟前缀?

使用中间件和前缀是为了方便管理和区分不同的请求,且提高路由规则的可读性和可维护性。 中间件就像是一个能够对请求进行预处理或过滤的工具箱,可以实现统一处理认证、权限控制等需要在多个路由上使用的逻辑。就像我们平时开车一样,脚下的刹车和油门就是车辆的中间件,可以控制车速和行驶方向。 前缀则是一个URL的字符串前缀,通常用于区分不同的路由组和层级结构。这就像一个家庭住址地址的层级结构一样,如国家、城市、区县、小区等,每个层级都拥有一个唯一标识,以便管理和寻址。

响应方式

除了使用 response()->json() 方法返回 JSON 响应外,Laravel 还提供了其他响应方式,例如: - view() 方法用于返回视图响应; - redirect() 方法用于重定向响应; - download() 方法用于下载文件响应; - streamDownload() 方法用于流式下载文件响应; - file() 方法用于返回文件响应; - response()->make() 方法用于创建自定义响应。 等等。

各种报错

1.POST 419 ; message: "CSRF token mismatch.", exception: "Symfony\Component\HttpKernel\Exception\HttpException",…}

  • 解决方案: 可以将路由添加到App\Http\Middleware\VerifyCsrfToken$except数组以禁用csrf对特定路由的检查。

image.png

  1. "message": "CSRF token mismatch.",
  • 由于 Laravel 框架中的 CSRF 中间件检测到请求中传递的 _token 参数与 Session 中存储的 token 值不匹配,从而导致请求被拒绝。这种安全机制可以避免 CSRF 攻击,也就是通过伪造页面提交 POST 请求来实现操作。
  • 解决方案:发送 AJAX 请求时,同时将当前页面的 _token 值作为参数一同传递给后端: data: { "_token": $('input[name=_token]').val(), "data": uniqueIds },在发送请求时,需要将请求类型改为 POST。
  • $('meta[name="csrf-token"]').attr('content') 表示获取当前页面的 _token 值。需要注意的是,如果在 blade 视图文件中使用了 csrf_field 方法,那么在页面中会自动生成一个名为 _token 的隐藏输入框,并且其值就是当前页面的 CSRF token。在这种情况下,我们可以通过 $('input[name=_token]').val() 获取到 CSRF token 值。