commit b76fb31890c737b0ed663011bed03de288833c74 Author: Snowz <372492339@qq.com> Date: Mon Apr 14 16:35:05 2025 +0800 new diff --git a/README.md b/README.md new file mode 100644 index 0000000..3f19477 --- /dev/null +++ b/README.md @@ -0,0 +1,72 @@ +# Modern QRCode Generator + +这是一个基于开源项目 [PHP QR Code](http://phpqrcode.sourceforge.net/) 重构的现代化二维码生成工具。我们对原项目进行了全面的改进和优化,同时保留了其核心功能和开源精神。 + +## 主要改进 + +- 使用 Laravel 框架重构后端,提供更好的代码组织和安全性 +- 采用 Vue.js 构建响应式前端界面,提供更好的用户体验 +- 增加二维码样式自定义功能 +- 支持二维码 Logo 添加 +- 实现二维码生成缓存机制 +- 完善的API文档和单元测试 + +## 技术栈 + +- 后端:Laravel 10.x +- 前端:Vue 3 + Vite +- UI框架:Element Plus +- 二维码生成:PHP QR Code Library +- 缓存:Redis + +## 快速开始 + +### 环境要求 + +- PHP >= 8.1 +- Composer +- Node.js >= 16 +- Redis + +### 安装步骤 + +1. 克隆项目 +```bash +git clone [项目地址] +cd modern-qrcode +``` + +2. 安装后端依赖 +```bash +composer install +cp .env.example .env +php artisan key:generate +``` + +3. 安装前端依赖 +```bash +cd frontend +npm install +``` + +4. 启动开发服务器 +```bash +# 后端 +php artisan serve + +# 前端 +cd frontend +npm run dev +``` + +## API文档 + +访问 `/api/documentation` 查看完整的API文档。 + +## 致谢 + +感谢原项目 [PHP QR Code](http://phpqrcode.sourceforge.net/) 提供的优秀代码基础,本项目在其基础上进行了现代化改造。原项目采用LGPL 3协议开源。 + +## 许可证 + +本项目采用 MIT 许可证。 \ No newline at end of file diff --git a/app/Http/Controllers/QRCodeController.php b/app/Http/Controllers/QRCodeController.php new file mode 100644 index 0000000..78438a2 --- /dev/null +++ b/app/Http/Controllers/QRCodeController.php @@ -0,0 +1,73 @@ +qrCodeService = $qrCodeService; + } + + public function generate(Request $request) + { + $validator = Validator::make($request->all(), [ + 'text' => 'required|string|max:2048', + 'size' => 'required|integer|min:100|max:800', + 'errorCorrection' => 'required|in:L,M,Q,H', + 'logo' => 'nullable|string' + ]); + + if ($validator->fails()) { + return response()->json(['errors' => $validator->errors()], 422); + } + + $cacheKey = md5($request->text . $request->size . $request->errorCorrection . $request->logo); + + try { + $qrCode = Cache::remember($cacheKey, 3600, function () use ($request) { + return $this->qrCodeService->generate( + $request->text, + $request->size, + $request->errorCorrection, + $request->logo + ); + }); + + return response($qrCode) + ->header('Content-Type', 'image/png') + ->header('Cache-Control', 'public, max-age=3600'); + + } catch (\Exception $e) { + return response()->json(['error' => '二维码生成失败'], 500); + } + } + + public function uploadLogo(Request $request) + { + $validator = Validator::make($request->all(), [ + 'file' => 'required|image|mimes:jpeg,png,jpg|max:2048' + ]); + + if ($validator->fails()) { + return response()->json(['errors' => $validator->errors()], 422); + } + + try { + $path = $request->file('file')->store('logos', 'public'); + return response()->json([ + 'url' => asset('storage/' . $path) + ]); + } catch (\Exception $e) { + return response()->json(['error' => 'Logo上传失败'], 500); + } + } +} \ No newline at end of file diff --git a/app/Services/QRCodeService.php b/app/Services/QRCodeService.php new file mode 100644 index 0000000..ccb7902 --- /dev/null +++ b/app/Services/QRCodeService.php @@ -0,0 +1,78 @@ +tempDir = storage_path('app/temp'); + if (!file_exists($this->tempDir)) { + mkdir($this->tempDir, 0777, true); + } + + // 引入PHP QR Code库 + require_once base_path('vendor/phpqrcode/qrlib.php'); + $this->phpqrcode = new \QRcode(); + } + + public function generate(string $text, int $size, string $errorCorrection = 'M', ?string $logoPath = null) + { + $tempFile = $this->tempDir . '/' . md5($text . time()) . '.png'; + + // 设置错误纠正级别 + $errorCorrectionLevel = match($errorCorrection) { + 'L' => QR_ECLEVEL_L, + 'M' => QR_ECLEVEL_M, + 'Q' => QR_ECLEVEL_Q, + 'H' => QR_ECLEVEL_H, + default => QR_ECLEVEL_M + }; + + try { + // 生成二维码 + $this->phpqrcode->png($text, $tempFile, $errorCorrectionLevel, $size); + + // 如果提供了Logo,则合并Logo + if ($logoPath) { + $this->mergeLogo($tempFile, $logoPath); + } + + // 读取生成的图片 + $qrCode = file_get_contents($tempFile); + + // 删除临时文件 + unlink($tempFile); + + return $qrCode; + + } catch (Exception $e) { + if (file_exists($tempFile)) { + unlink($tempFile); + } + throw $e; + } + } + + protected function mergeLogo(string $qrCodePath, string $logoPath) + { + $qrCode = Image::make($qrCodePath); + $logo = Image::make($logoPath); + + // 调整Logo大小,不超过二维码的1/4 + $logoSize = min($qrCode->width(), $qrCode->height()) / 4; + $logo->resize($logoSize, $logoSize, function ($constraint) { + $constraint->aspectRatio(); + }); + + // 在二维码中心添加Logo + $qrCode->insert($logo, 'center'); + $qrCode->save($qrCodePath); + } +} \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..a58daf8 --- /dev/null +++ b/composer.json @@ -0,0 +1 @@ +{"name":"modern-qrcode/generator","type":"project","description":"A modern QR code generator built with Laravel","keywords":["qrcode","laravel","generator"],"license":"MIT","require":{"php":"^8.1","laravel/framework":"^10.0","guzzlehttp/guzzle":"^7.2","intervention/image":"^2.7","predis/predis":"^2.0"},"require-dev":{"fakerphp/faker":"^1.9.1","laravel/pint":"^1.0","laravel/sail":"^1.18","mockery/mockery":"^1.4.4","nunomaduro/collision":"^7.0","phpunit/phpunit":"^10.1","spatie/laravel-ignition":"^2.0"},"autoload":{"psr-4":{"App\\":"app/","Database\\Factories\\":"database/factories/","Database\\Seeders\\":"database/seeders/"}},"autoload-dev":{"psr-4":{"Tests\\":"tests/"}},"scripts":{"post-autoload-dump":["Illuminate\\Foundation\\ComposerScripts::postAutoloadDump","@php artisan package:discover --ansi"],"post-update-cmd":["@php artisan vendor:publish --tag=laravel-assets --ansi --force"],"post-root-package-install":["@php -r \"file_exists('.env') || copy('.env.example', '.env');\""],"post-create-project-cmd":["@php artisan key:generate --ansi"]},"extra":{"laravel":{"dont-discover":[]}},"config":{"optimize-autoloader":true,"preferred-install":"dist","sort-packages":true,"allow-plugins":{"pestphp/pest-plugin":true,"php-http/discovery":true}}} \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..2e4ea5f --- /dev/null +++ b/frontend/package.json @@ -0,0 +1 @@ +{"name":"modern-qrcode-frontend","private":true,"version":"0.0.0","type":"module","scripts":{"dev":"vite","build":"vite build","preview":"vite preview"},"dependencies":{"@element-plus/icons-vue":"^2.1.0","axios":"^1.6.0","element-plus":"^2.4.0","pinia":"^2.1.0","vue":"^3.3.0","vue-router":"^4.2.0"},"devDependencies":{"@vitejs/plugin-vue":"^4.5.0","sass":"^1.69.0","unplugin-auto-import":"^0.16.0","unplugin-vue-components":"^0.25.0","vite":"^5.0.0"}} \ No newline at end of file diff --git a/frontend/src/App.vue b/frontend/src/App.vue new file mode 100644 index 0000000..3e75a63 --- /dev/null +++ b/frontend/src/App.vue @@ -0,0 +1,212 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/main.js b/frontend/src/main.js new file mode 100644 index 0000000..e3888a1 --- /dev/null +++ b/frontend/src/main.js @@ -0,0 +1,15 @@ +import { createApp } from 'vue' +import { createPinia } from 'pinia' +import ElementPlus from 'element-plus' +import 'element-plus/dist/index.css' +import App from './App.vue' +import router from './router' +import './assets/main.css' + +const app = createApp(App) + +app.use(createPinia()) +app.use(router) +app.use(ElementPlus) + +app.mount('#app') \ No newline at end of file diff --git a/frontend/vite.config.js b/frontend/vite.config.js new file mode 100644 index 0000000..f233ff7 --- /dev/null +++ b/frontend/vite.config.js @@ -0,0 +1,25 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import AutoImport from 'unplugin-auto-import/vite' +import Components from 'unplugin-vue-components/vite' +import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' + +export default defineConfig({ + plugins: [ + vue(), + AutoImport({ + resolvers: [ElementPlusResolver()], + }), + Components({ + resolvers: [ElementPlusResolver()], + }), + ], + server: { + proxy: { + '/api': { + target: 'http://localhost:8000', + changeOrigin: true, + } + } + } +}) \ No newline at end of file