diff --git a/EMAIL_NOTIFICATION_IMPLEMENTATION_SUMMARY.md b/EMAIL_NOTIFICATION_IMPLEMENTATION_SUMMARY.md deleted file mode 100644 index 09d2a23..0000000 --- a/EMAIL_NOTIFICATION_IMPLEMENTATION_SUMMARY.md +++ /dev/null @@ -1,228 +0,0 @@ -# 拜访邮件通知功能实现总结 - -## 实现内容 - -已成功将 `api/mobile/visit/visit-save` 方法中的短信通知改为邮件通知,并为admin数据表增加了邮箱字段。 - -## 修改详情 - -### 1. 数据库迁移 - -#### 新增邮箱字段迁移 (`2025_09_09_131331_add_email_to_admins_table.php`) -```php -public function up() -{ - Schema::table('admins', function (Blueprint $table) { - $table->string('email')->nullable()->comment('邮箱地址'); - }); -} - -public function down() -{ - Schema::table('admins', function (Blueprint $table) { - $table->dropColumn('email'); - }); -} -``` - -### 2. 邮件通知类 - -#### 创建专门的拜访邮件通知类 (`app/Notifications/VisitEmailNotify.php`) -```php -class VisitEmailNotify extends Notification -{ - protected $visitData; - - public function __construct($visitData) - { - $this->visitData = $visitData; - } - - public function via($notifiable) - { - return ['mail']; - } - - public function toMail($notifiable) - { - $visitData = $this->visitData; - - return (new MailMessage) - ->subject(__('mobile.visit.email_subject', ['name' => $visitData['name']])) - ->greeting(__('mobile.visit.email_greeting', ['admin_name' => $notifiable->name])) - ->line(__('mobile.visit.email_content_line1', [ - 'visitor_name' => $visitData['name'], - 'visit_date' => $visitData['date'] - ])) - ->line(__('mobile.visit.email_content_line2', [ - 'visitor_mobile' => $visitData['mobile'] - ])) - ->line(__('mobile.visit.email_content_line3', [ - 'visit_reason' => $visitData['reason'] ?? __('mobile.visit.no_reason') - ])) - ->action(__('mobile.visit.email_action'), url('/admin/visits')) - ->line(__('mobile.visit.email_footer')); - } -} -``` - -### 3. 控制器修改 - -#### VisitController.php 中的 visitSave 方法 -```php -// 修改前:短信通知 -$vars = ['date' => $all['date'], 'name' => $all['name'], 'phone_number' => $all['mobile']]; -$template_id = '5wReg1'; -sms($acceptAdmin->mobile, $vars, $template_id); - -// 修改后:邮件通知 -if ($acceptAdmin && $acceptAdmin->email) { - $visitData = [ - 'name' => $all['name'], - 'date' => $all['date'], - 'mobile' => $all['mobile'], - 'reason' => $all['reason'] ?? null, - ]; - $acceptAdmin->notify(new VisitEmailNotify($visitData)); -} -``` - -### 4. 多语言支持 - -#### 中文语言文件 (`lang/zh-CN/mobile.php`) -```php -'visit' => [ - // ... 其他翻译 - 'email_subject' => '新的拜访预约通知 - :name', - 'email_greeting' => '您好 :admin_name,', - 'email_content_line1' => '您有一个新的拜访预约,访客 :visitor_name 预约于 :visit_date 到访。', - 'email_content_line2' => '访客联系电话::visitor_mobile', - 'email_content_line3' => '拜访事由::visit_reason', - 'email_action' => '查看详情', - 'email_footer' => '请及时处理此拜访预约。', - 'no_reason' => '无', -], -``` - -#### 英文语言文件 (`lang/en/mobile.php`) -```php -'visit' => [ - // ... 其他翻译 - 'email_subject' => 'New Visit Appointment Notification - :name', - 'email_greeting' => 'Hello :admin_name,', - 'email_content_line1' => 'You have a new visit appointment. Visitor :visitor_name is scheduled to visit on :visit_date.', - 'email_content_line2' => 'Visitor contact phone: :visitor_mobile', - 'email_content_line3' => 'Visit reason: :visit_reason', - 'email_action' => 'View Details', - 'email_footer' => 'Please handle this visit appointment promptly.', - 'no_reason' => 'None', -], -``` - -### 5. 模型工厂 - -#### 为测试创建了必要的模型工厂 -- `AdminFactory.php` - Admin模型工厂 -- `DepartmentFactory.php` - Department模型工厂 -- `VisitAreaFactory.php` - VisitArea模型工厂 - -#### 为模型添加了HasFactory trait -- `Admin.php` - 添加了 `use HasFactory;` -- `Department.php` - 添加了 `use HasFactory;` -- `VisitArea.php` - 添加了 `use HasFactory;` - -## 邮件内容示例 - -### 中文邮件 -``` -主题:新的拜访预约通知 - 张三 - -您好 李管理员, - -您有一个新的拜访预约,访客 张三 预约于 2024-01-15 到访。 - -访客联系电话:13800138000 - -拜访事由:商务洽谈 - -[查看详情] 按钮 - -请及时处理此拜访预约。 -``` - -### 英文邮件 -``` -Subject: New Visit Appointment Notification - John - -Hello Admin, - -You have a new visit appointment. Visitor John is scheduled to visit on 2024-01-15. - -Visitor contact phone: 13800138000 - -Visit reason: Business meeting - -[View Details] button - -Please handle this visit appointment promptly. -``` - -## 功能特点 - -1. **多语言支持**: 根据系统语言设置自动切换邮件语言 -2. **条件发送**: 只有当管理员有邮箱地址时才发送邮件 -3. **详细信息**: 包含访客姓名、预约日期、联系电话、拜访事由等完整信息 -4. **操作链接**: 提供直接跳转到管理后台的链接 -5. **优雅降级**: 如果没有拜访事由,显示"无"或"None" - -## 测试验证 - -创建了完整的测试用例验证邮件通知功能: - -```php -/** @test */ -public function test_visit_email_notification_content() -{ - // 设置中文语言环境 - app()->setLocale('zh-CN'); - - $admin = Admin::factory()->create([ - 'name' => '测试管理员', - 'email' => 'test@example.com' - ]); - - $visitData = [ - 'name' => '测试访客', - 'date' => '2024-01-15', - 'mobile' => '13800138000', - 'reason' => '商务洽谈', - ]; - - $notification = new VisitEmailNotify($visitData); - $mailMessage = $notification->toMail($admin); - - // 验证邮件内容 - $this->assertStringContainsString('新的拜访预约通知', $mailMessage->subject); - $this->assertStringContainsString('测试访客', $mailMessage->subject); - // ... 其他验证 -} -``` - -## 优势 - -1. **更环保**: 邮件通知比短信更环保 -2. **更丰富**: 邮件可以包含更多信息和格式 -3. **更便宜**: 邮件发送成本通常比短信低 -4. **更可靠**: 邮件服务通常比短信服务更稳定 -5. **更灵活**: 邮件内容可以根据需要自定义格式 - -## 总结 - -已成功将拜访预约的短信通知改为邮件通知,实现了: -- ✅ admin表增加email字段 -- ✅ 创建专门的邮件通知类 -- ✅ 支持中英双语邮件内容 -- ✅ 完整的测试覆盖 -- ✅ 保持原有审核流程不变 - -现在当用户提交拜访预约时,系统会自动向被访管理员发送包含详细信息的邮件通知,提供更好的用户体验。 diff --git a/ERROR_RESPONSE_UNIFICATION_SUMMARY.md b/ERROR_RESPONSE_UNIFICATION_SUMMARY.md deleted file mode 100644 index 1300960..0000000 --- a/ERROR_RESPONSE_UNIFICATION_SUMMARY.md +++ /dev/null @@ -1,100 +0,0 @@ -# 错误响应方法统一修正总结 - -## 修正内容 - -已将所有控制器中的 `$this->error()` 调用统一修正为 `$this->fail()` 调用,确保整个系统使用统一的错误响应格式。 - -## 修正的文件 - -### 1. GateController.php -修正了 4 处 `$this->error()` 调用: - -```php -// 修正前 -return $this->error(__('gate.get_visit_list_failed')); -return $this->error(__('gate.get_visit_detail_failed')); -return $this->error(__('gate.person_no_required')); -return $this->error(__('gate.operation_failed') . ':' . $e->getMessage()); - -// 修正后 -return $this->fail([ResponseCode::ERROR_INSIDE, __('gate.get_visit_list_failed')]); -return $this->fail([ResponseCode::ERROR_INSIDE, __('gate.get_visit_detail_failed')]); -return $this->fail([ResponseCode::ERROR_PARAMETER, __('gate.person_no_required')]); -return $this->fail([ResponseCode::ERROR_INSIDE, __('gate.operation_failed') . ':' . $e->getMessage()]); -``` - -### 2. VisitLogController.php -修正了 7 处 `$this->error()` 调用: - -```php -// 修正前 -return $this->error(__('visit_log.visit_id_required')); -return $this->error(__('visit_log.type_required')); -return $this->error(__('visit_log.not_found')); -return $this->error(__('visit_log.save_failed') . ':' . $e->getMessage()); -return $this->error(__('visit_log.id_required')); -return $this->error(__('visit_log.not_found')); -return $this->error(__('visit_log.delete_failed') . ':' . $e->getMessage()); - -// 修正后 -return $this->fail([ResponseCode::ERROR_PARAMETER, __('visit_log.visit_id_required')]); -return $this->fail([ResponseCode::ERROR_PARAMETER, __('visit_log.type_required')]); -return $this->fail([ResponseCode::ERROR_BUSINESS, __('visit_log.not_found')]); -return $this->fail([ResponseCode::ERROR_INSIDE, __('visit_log.save_failed') . ':' . $e->getMessage()]); -return $this->fail([ResponseCode::ERROR_PARAMETER, __('visit_log.id_required')]); -return $this->fail([ResponseCode::ERROR_BUSINESS, __('visit_log.not_found')]); -return $this->fail([ResponseCode::ERROR_INSIDE, __('visit_log.delete_failed') . ':' . $e->getMessage()]); -``` - -### 3. SendVisitReminderCommand.php -**未修改** - 该文件中的 `$this->error()` 是 Laravel 命令类的标准方法,用于控制台输出,不需要修改。 - -## 错误码分类 - -根据错误类型使用了不同的错误码: - -### ERROR_PARAMETER (10001) - 参数错误 -- 缺少必填参数 -- 参数格式错误 - -### ERROR_BUSINESS (10002) - 业务错误 -- 记录不存在 -- 业务逻辑验证失败 - -### ERROR_INSIDE (10003) - 内部错误 -- 数据库操作失败 -- 系统异常 - -## 响应格式 - -所有错误响应现在都使用统一格式: - -```json -{ - "errcode": 10001, - "errmsg": "错误消息" -} -``` - -## 测试结果 - -✅ **门岗端接口测试通过** -- ✅ 拜访列表接口 -- ✅ 拜访详情接口 -- ✅ 拜访更新接口 -- ✅ 多语言支持 -- ✅ API响应格式 -- ✅ 路由验证 - -⚠️ **认证测试失败**(之前存在的问题,与本次修改无关) - -## 优势 - -1. **统一性**: 整个系统使用相同的错误响应格式 -2. **标准化**: 错误码分类清晰,便于前端处理 -3. **可维护性**: 统一的错误处理逻辑 -4. **一致性**: 与系统其他部分保持一致的响应格式 - -## 总结 - -已成功将所有 `$this->error()` 调用统一修正为 `$this->fail()` 调用,确保整个系统使用统一的错误响应格式。所有门岗端接口测试通过,系统响应格式完全统一。 diff --git a/FILE_FIELDS_SUMMARY.md b/FILE_FIELDS_SUMMARY.md deleted file mode 100644 index 435e871..0000000 --- a/FILE_FIELDS_SUMMARY.md +++ /dev/null @@ -1,153 +0,0 @@ -# 文件字段修正总结 - -## 修正内容 - -根据要求,已对所有涉及文件上传的字段进行了修正,确保: - -1. **多文件字段**:设计成json类型,存储uploads表的id数组 -2. **单文件字段**:设计成int类型,直接存储uploads表的id -3. **模型关联**:json类型通过getFilesAttribute方法自动关联uploads表,int类型直接通过belongsTo关联 - -## 修正的模型和字段 - -### 1. Visit模型 (visits表) - -**字段修正**: -- `accept_admin_sign` (string → unsignedBigInteger) - 被访人签字图片id(单文件) -- `file` (json) - 附件id数组(多文件) - -**关联关系**: -```php -// 单文件关联 -public function acceptAdminSign() -{ - return $this->belongsTo(Upload::class, 'accept_admin_sign'); -} - -// 多文件关联 -public function getFilesAttribute() -{ - if (!$this->file) { - return []; - } - return Upload::whereIn('id', $this->file)->get(); -} -``` - -### 2. Study模型 (studies表) - -**字段**: -- `file` (json) - 文件id数组(多文件) - -**关联关系**: -```php -// 多文件关联 -public function getFilesAttribute() -{ - if (!$this->file) { - return []; - } - return Upload::whereIn('id', $this->file)->get(); -} -``` - -### 3. Blacklist模型 (blacklists表) - -**字段**: -- `file` (json) - 附件id数组(多文件) - -**关联关系**: -```php -// 多文件关联 -public function getFilesAttribute() -{ - if (!$this->file) { - return []; - } - return Upload::whereIn('id', $this->file)->get(); -} -``` - -## 控制器更新 - -### 1. VisitController -- 在show方法中添加了 `acceptAdminSign` 关联关系 -- 确保返回数据时包含签字图片信息 - -### 2. GateController -- 在visits和visitDetail方法中添加了 `acceptAdminSign` 关联关系 -- 确保门岗端也能获取到签字图片信息 - -## 数据库迁移 - -### 1. 字段类型修正 -创建了迁移文件 `2025_09_09_111215_fix_accept_admin_sign_field_type_in_visits_table.php`: -- 将 `accept_admin_sign` 字段从 `string` 类型改为 `unsignedBigInteger` 类型 -- 确保单文件字段使用正确的数据类型 - -## 使用方式 - -### 1. 单文件字段使用 -```php -// 获取签字图片 -$visit = Visit::with('acceptAdminSign')->find(1); -$signImage = $visit->acceptAdminSign; // 返回Upload模型实例 - -// 设置签字图片 -$visit->accept_admin_sign = 123; // 直接设置uploads表的id -$visit->save(); -``` - -### 2. 多文件字段使用 -```php -// 获取附件文件列表 -$visit = Visit::find(1); -$files = $visit->files; // 返回Upload模型集合 - -// 设置附件文件 -$visit->file = [123, 456, 789]; // 设置uploads表的id数组 -$visit->save(); -``` - -### 3. API返回数据格式 -```json -{ - "id": 1, - "name": "访客姓名", - "accept_admin_sign": { - "id": 123, - "original_name": "signature.jpg", - "url": "http://domain.com/uploads/signature.jpg" - }, - "files": [ - { - "id": 456, - "original_name": "document1.pdf", - "url": "http://domain.com/uploads/document1.pdf" - }, - { - "id": 789, - "original_name": "document2.pdf", - "url": "http://domain.com/uploads/document2.pdf" - } - ] -} -``` - -## 验证结果 - -1. ✅ **字段类型正确**:单文件使用int类型,多文件使用json类型 -2. ✅ **关联关系完整**:所有文件字段都有对应的关联关系 -3. ✅ **控制器更新**:相关控制器已更新with方法包含文件关联 -4. ✅ **数据库迁移**:字段类型已通过迁移文件修正 -5. ✅ **类型转换**:模型中的casts配置正确 - -## 注意事项 - -1. **文件存储**:所有文件都存储在uploads表中,通过id关联 -2. **URL生成**:Upload模型自动生成文件访问URL -3. **关联加载**:使用with()方法预加载文件关联,避免N+1查询 -4. **数据验证**:上传文件时需要验证文件id是否存在于uploads表中 -5. **软删除**:文件删除时使用软删除,保持数据完整性 - -所有文件字段已按照要求修正完成,可以正常使用! diff --git a/GATE_CONTROLLER_UPDATE_SUMMARY.md b/GATE_CONTROLLER_UPDATE_SUMMARY.md deleted file mode 100644 index 054d4a8..0000000 --- a/GATE_CONTROLLER_UPDATE_SUMMARY.md +++ /dev/null @@ -1,124 +0,0 @@ -# 门岗端控制器统一响应格式更新总结 - -## 更新内容 - -根据要求,已将门岗端控制器的响应方法统一为系统标准的 `success` 和 `fail` 方法,移除了自定义的响应方法。 - -## 修改详情 - -### 1. 移除自定义方法 -- 删除了 `GateController` 中自定义的 `success()` 和 `error()` 方法 -- 使用系统统一的 `ApiResponse` trait 中的方法 - -### 2. 更新错误响应 -将所有 `$this->error()` 调用更新为 `$this->fail()` 调用: - -```php -// 修改前 -return $this->error(__('gate.visit_not_found')); - -// 修改后 -return $this->fail([ResponseCode::ERROR_BUSINESS, __('gate.visit_not_found')]); -``` - -### 3. 统一响应格式 -- **成功响应**: 使用 `$this->success($data)` 直接返回数据 -- **失败响应**: 使用 `$this->fail([$errcode, $errmsg])` 返回错误码和消息 - -## 响应格式 - -### 成功响应格式 -```json -{ - "current_page": 1, - "data": [...], - "total": 10 -} -``` - -### 失败响应格式 -```json -{ - "errcode": 10002, - "errmsg": "错误消息" -} -``` - -## 更新的错误类型 - -1. **拜访记录不存在** - ```php - return $this->fail([ResponseCode::ERROR_BUSINESS, __('gate.visit_not_found')]); - ``` - -2. **只有已审核通过的拜访才能进厂** - ```php - return $this->fail([ResponseCode::ERROR_BUSINESS, __('gate.only_approved_visits_can_enter')]); - ``` - -3. **只有已进厂的访客才能离厂** - ```php - return $this->fail([ResponseCode::ERROR_BUSINESS, __('gate.only_entered_visitors_can_leave')]); - ``` - -4. **只有物流车辆才能上传货车图片** - ```php - return $this->fail([ResponseCode::ERROR_BUSINESS, __('gate.only_logistics_cars_can_upload_images')]); - ``` - -5. **货车图片不能为空** - ```php - return $this->fail([ResponseCode::ERROR_BUSINESS, __('gate.vehicle_images_required')]); - ``` - -6. **货车图片ID无效** - ```php - return $this->fail([ResponseCode::ERROR_BUSINESS, __('gate.invalid_vehicle_images')]); - ``` - -7. **无效的操作类型** - ```php - return $this->fail([ResponseCode::ERROR_BUSINESS, __('gate.invalid_action_type')]); - ``` - -## 测试结果 - -### ✅ 通过的测试 -- ✅ 门岗端拜访列表接口 -- ✅ 门岗端拜访详情接口 -- ✅ 门岗端拜访更新接口 -- ✅ 多语言支持 -- ✅ API响应格式 -- ✅ 路由验证 - -### ⚠️ 需要关注的问题 -- ⚠️ 后台管理接口认证测试失败(可能是中间件配置问题) - -## 代码变更 - -### 添加的引用 -```php -use App\Helpers\ResponseCode; -``` - -### 移除的方法 -```php -// 删除了这些自定义方法 -public function success($data = null, $message = 'success') { ... } -public function error($message = 'error', $code = 400, $data = null) { ... } -``` - -### 更新的调用 -- 所有 `$this->error()` → `$this->fail([ResponseCode::ERROR_BUSINESS, $message])` -- 保持 `$this->success($data)` 不变 - -## 优势 - -1. **统一性**: 与系统其他控制器使用相同的响应格式 -2. **一致性**: 错误码和消息格式统一 -3. **可维护性**: 减少了重复代码 -4. **标准化**: 符合系统架构规范 - -## 总结 - -门岗端控制器已成功更新为使用系统统一的响应方法,所有接口都能正常工作,响应格式与系统其他部分保持一致。测试显示主要功能正常,只有认证相关的测试需要进一步检查。 diff --git a/MOBILE_MULTILINGUAL_SUMMARY.md b/MOBILE_MULTILINGUAL_SUMMARY.md deleted file mode 100644 index 05a4c17..0000000 --- a/MOBILE_MULTILINGUAL_SUMMARY.md +++ /dev/null @@ -1,206 +0,0 @@ -# 前台Mobile端多语言支持实现总结 - -## 实现内容 - -已成功为前台mobile端所有接口添加中英双语支持,确保所有错误消息和响应文本都能根据 `Accept-Language` 头部自动切换语言。 - -## 修改的文件 - -### 1. 语言文件 - -#### 中文语言文件 (`lang/zh-CN/mobile.php`) -```php - [ - 'code_required' => 'code必填', - 'code_invalid' => 'code异常', - 'update_success' => '更新成功', - 'mobile_required' => 'code必填', - 'id_required' => 'Id必填', - ], - - // 拜访管理 - 'visit' => [ - 'name_required' => '名称必填', - 'visit_area_id_required' => '区域id必填', - 'accept_admin_id_required' => '接待人必填', - 'accompany_id_required' => '陪同人必填', - 'accept_admin_department_not_exists' => '接待人部门不存在', - 'accept_admin_department_manager_not_exists' => '接待人部门不存在负责人', - 'update_success' => '更新成功', - 'type_required' => '类型必填', - 'not_studied' => '未学习', - 'study_expired' => '学习过期', - 'study_valid' => '学习有效中', - 'idcard_required' => '身份证数组必填', - ], - - // 通用 - 'common' => [ - 'mobile_required' => '手机号必填', - 'mobile_numeric' => '手机号格式错误', - 'type_required' => '类型必填', - 'send_too_frequent' => '请勿频繁发送', - 'send_success' => '发送成功', - 'send_failed' => '发送失败', - ], -]; -``` - -#### 英文语言文件 (`lang/en/mobile.php`) -```php - [ - 'code_required' => 'Code is required', - 'code_invalid' => 'Code is invalid', - 'update_success' => 'Update successful', - 'mobile_required' => 'Code is required', - 'id_required' => 'Id is required', - ], - - // Visit Management - 'visit' => [ - 'name_required' => 'Name is required', - 'visit_area_id_required' => 'Visit area id is required', - 'accept_admin_id_required' => 'Accept admin is required', - 'accompany_id_required' => 'Accompany person is required', - 'accept_admin_department_not_exists' => 'Accept admin department does not exist', - 'accept_admin_department_manager_not_exists' => 'Accept admin department manager does not exist', - 'update_success' => 'Update successful', - 'type_required' => 'Type is required', - 'not_studied' => 'Not studied', - 'study_expired' => 'Study expired', - 'study_valid' => 'Study is valid', - 'idcard_required' => 'Id card array is required', - ], - - // Common - 'common' => [ - 'mobile_required' => 'Mobile is required', - 'mobile_numeric' => 'Mobile format error', - 'type_required' => 'Type is required', - 'send_too_frequent' => 'Please do not send too frequently', - 'send_success' => 'Send successful', - 'send_failed' => 'Send failed', - ], -]; -``` - -### 2. 控制器修改 - -#### UserController.php -- 修正了所有硬编码的中文错误消息 -- 使用 `__('mobile.user.xxx')` 格式调用多语言文本 -- 修正了 `Config::get()` 为 `Config::all()` 的调用错误 - -#### VisitController.php -- 修正了所有硬编码的中文错误消息 -- 使用 `__('mobile.visit.xxx')` 格式调用多语言文本 -- 包括验证消息、业务错误消息和成功消息 - -#### CommonController.php -- 修正了所有硬编码的中文错误消息 -- 使用 `__('mobile.common.xxx')` 格式调用多语言文本 - -### 3. 路由配置修改 - -#### routes/api.php -```php -// 前台 -Route::get('mobile/user/login', [\App\Http\Controllers\Mobile\UserController::class, 'login'])->middleware('set.locale'); -Route::get('mobile/user/config', [\App\Http\Controllers\Mobile\UserController::class, 'config'])->middleware('set.locale'); -Route::group(["namespace" => "Mobile", "prefix" => "mobile", "middleware" => ["sanctum.jwt:mobile", "set.locale"]], function () { - // ... 其他路由 -}); -``` - -## 支持的接口 - -### 用户管理接口 -- `GET /api/mobile/user/login` - 用户登录 -- `GET /api/mobile/user/config` - 获取配置信息 -- `POST /api/mobile/user/save` - 更新用户信息 -- `GET /api/mobile/user/mobile` - 获取手机号 -- `GET /api/mobile/user/show` - 获取用户信息 -- `GET /api/mobile/user/my-visit` - 我的拜访列表 -- `GET /api/mobile/user/my-visit-detail` - 我的拜访详情 - -### 拜访管理接口 -- `POST /api/mobile/visit/visit-save` - 保存拜访 -- `GET /api/mobile/visit/get-ask` - 获取学习内容 -- `GET /api/mobile/visit/visit-area` - 拜访区域列表 -- `GET /api/mobile/visit/visit-time` - 拜访时间段列表 -- `GET /api/mobile/visit/ask-log` - 获取学习记录 -- `POST /api/mobile/visit/ask-save` - 保存学习记录 -- `POST /api/mobile/visit/idcard-check` - 身份证检查 - -### 通用接口 -- `GET /api/mobile/common/send-sms` - 发送短信 - -## 多语言使用方式 - -### 客户端请求 -```javascript -// 中文请求 -fetch('/api/mobile/user/login', { - headers: { - 'Accept-Language': 'zh-CN' - } -}); - -// 英文请求 -fetch('/api/mobile/user/login', { - headers: { - 'Accept-Language': 'en' - } -}); -``` - -### 响应示例 - -#### 中文响应 -```json -{ - "errcode": 10001, - "errmsg": "code必填" -} -``` - -#### 英文响应 -```json -{ - "errcode": 10001, - "errmsg": "Code is required" -} -``` - -## 测试结果 - -✅ **所有测试通过** -- ✅ mobile login endpoint supports multilingual -- ✅ mobile config endpoint supports multilingual -- ✅ mobile visit area endpoint supports multilingual -- ✅ mobile visit time endpoint supports multilingual - -## 技术实现 - -1. **中间件支持**: 所有mobile端路由都应用了 `set.locale` 中间件 -2. **语言检测**: 通过 `Accept-Language` 头部自动检测语言 -3. **统一响应**: 使用 `ApiResponse` trait 确保响应格式一致 -4. **错误码标准化**: 使用 `ResponseCode` 常量确保错误码统一 - -## 优势 - -1. **国际化支持**: 完整的中英双语支持 -2. **用户体验**: 根据用户语言偏好自动切换 -3. **维护性**: 集中的语言文件管理 -4. **一致性**: 与系统其他部分保持统一的响应格式 -5. **扩展性**: 易于添加更多语言支持 - -## 总结 - -前台mobile端已成功实现完整的中英双语支持,所有接口都能根据 `Accept-Language` 头部自动切换语言,提供更好的国际化用户体验。 diff --git a/MULTILINGUAL_API.md b/MULTILINGUAL_API.md deleted file mode 100644 index 377d32a..0000000 --- a/MULTILINGUAL_API.md +++ /dev/null @@ -1,183 +0,0 @@ -# 多语言API接口文档 - -## 概述 - -本系统已实现完整的中英双语多语言支持,所有API接口都支持通过HTTP头动态语言切换。 - -## 支持的语言 - -- `zh-CN`: 简体中文(默认) -- `en`: 英文 - -## 语言切换方式 - -### 通过HTTP头切换 - -```bash -# 设置Accept-Language头为中文 -Accept-Language: zh-CN - -# 设置Accept-Language头为英文 -Accept-Language: en -``` - -## 接口示例 - -### 门岗端接口 - -#### 获取拜访列表(中文) -```bash -GET /api/gate/visits?date=2024-01-15 -Accept-Language: zh-CN -``` - -响应: -```json -{ - "code": 200, - "message": "success", - "data": { - "current_page": 1, - "data": [...], - "total": 10 - } -} -``` - -#### 获取拜访列表(英文) -```bash -GET /api/gate/visits?date=2024-01-15 -Accept-Language: en -``` - -### 后台管理接口 - -#### 获取拜访日志列表(中文) -```bash -GET /api/admin/visit-logs/index?token=your_token -Accept-Language: zh-CN -``` - -#### 获取拜访日志列表(英文) -```bash -GET /api/admin/visit-logs/index?token=your_token -Accept-Language: en -``` - -## 错误消息多语言 - -所有错误消息都支持多语言: - -### 中文错误消息 -```json -{ - "code": 400, - "message": "拜访ID不能为空", - "data": null -} -``` - -### 英文错误消息 -```json -{ - "code": 400, - "message": "Visit ID is required", - "data": null -} -``` - -## 语言文件结构 - -``` -lang/ -├── zh-CN/ -│ ├── common.php # 通用消息 -│ ├── gate.php # 门岗端消息 -│ ├── visit.php # 拜访相关消息 -│ ├── visit_log.php # 拜访日志消息 -│ └── ... -└── en/ - ├── common.php # Common messages - ├── gate.php # Gate interface messages - ├── visit.php # Visit related messages - ├── visit_log.php # Visit log messages - └── ... -``` - -## 测试多语言功能 - -可以通过设置不同的Accept-Language头来测试多语言功能: - -```bash -# 测试中文响应 -curl -H "Accept-Language: zh-CN" http://your-domain/api/gate/visits - -# 测试英文响应 -curl -H "Accept-Language: en" http://your-domain/api/gate/visits -``` - -## 中间件说明 - -系统使用 `SetLocale` 中间件自动处理语言切换: - -1. 检查HTTP头 `Accept-Language` -2. 验证语言是否支持(zh-CN, en) -3. 默认使用 `zh-CN` - -## 前端集成建议 - -### JavaScript示例 -```javascript -// 请求时设置Accept-Language头 -function fetchVisits(lang = 'zh-CN') { - return fetch('/api/gate/visits', { - headers: { - 'Accept-Language': lang - } - }).then(response => response.json()); -} - -// 使用 -fetchVisits('en'); -``` - -### Vue.js示例 -```javascript -// 在Vue组件中 -export default { - data() { - return { - currentLang: 'zh-CN' - } - }, - methods: { - switchLanguage(lang) { - this.currentLang = lang; - }, - async fetchData() { - const response = await this.$http.get('/api/gate/visits', { - headers: { - 'Accept-Language': this.currentLang - } - }); - return response.data; - } - } -} -``` - -## 注意事项 - -1. 所有API接口都支持多语言,无需额外配置 -2. 通过设置 `Accept-Language` HTTP头来指定语言 -3. 不设置语言头则使用默认语言(zh-CN) -4. 错误消息和成功消息都会根据当前语言返回对应文本 -5. 支持的语言:zh-CN(中文)、en(英文) - -## 扩展新语言 - -如需添加新语言支持: - -1. 在 `lang/` 目录下创建新的语言文件夹 -2. 复制现有语言文件并翻译内容 -3. 在 `SetLocale` 中间件中添加新语言到 `$supportedLocales` 数组 diff --git a/PROJECT_DOCUMENTATION.md b/PROJECT_DOCUMENTATION.md new file mode 100644 index 0000000..1227d8a --- /dev/null +++ b/PROJECT_DOCUMENTATION.md @@ -0,0 +1,195 @@ +# 访客管理系统项目文档 + +## 项目概述 + +这是一个基于 Laravel 9 框架开发的企业访客管理系统,主要用于管理企业的访客预约、门岗登记、审核流程等功能。系统采用前后端分离架构,提供了完整的 API 接口。 + +## 主要功能模块 + +### 1. 访客预约管理 +- **功能描述**: 支持访客在线预约访问,填写访问信息、时间、区域等 +- **核心文件**: `app/Models/Visit.php`, `app/Http/Controllers/Mobile/VisitController.php` +- **访问类型**: + - 访客 (TYPE_VISITOR = 1) + - 访客车辆 (TYPE_VISITOR_CAR = 2) + - 物流车辆 (TYPE_LOGISTICS_CAR = 3) + +### 2. 审核流程管理 +- **功能描述**: 多级审核流程,支持审核状态跟踪 +- **核心文件**: `app/Models/VisitAudit.php`, `app/Http/Controllers/Admin/VisitAuditController.php` +- **审核状态**: + - 待学习 (AUDIT_STATUS_PENDING_STUDY = -1) + - 待审核 (AUDIT_STATUS_PENDING = 0) + - 通过/待进厂 (AUDIT_STATUS_APPROVED = 1) + - 驳回 (AUDIT_STATUS_REJECTED = 2) + - 已进厂 (AUDIT_STATUS_ENTERED = 3) + - 已离厂 (AUDIT_STATUS_LEFT = 4) + +### 3. 门岗管理系统 +- **功能描述**: 门岗端进行访客登记、ID卡绑定、进出厂记录等 +- **核心文件**: `app/Http/Controllers/GateController.php`, `app/Models/GateLog.php` +- **主要功能**: + - 访客列表查询和筛选 + - ID卡绑定 (bind_card) + - 进厂登记 (enter) + - 离厂登记 (leave) + - 货车图片上传 (upload_vehicle) + +### 4. 学习培训模块 +- **功能描述**: 访客进厂前的安全学习和考试 +- **核心文件**: `app/Models/Study.php`, `app/Models/StudyAsk.php`, `app/Models/StudyLog.php` +- **相关控制器**: `app/Http/Controllers/Admin/StudyController.php`, `StudyAskController.php` + +### 5. 系统配置管理 +- **功能描述**: 系统参数配置、访问区域管理、时间段管理等 +- **核心文件**: + - `app/Models/Config.php` - 系统配置 + - `app/Models/VisitArea.php` - 访问区域 + - `app/Models/VisitTime.php` - 访问时间段 + - `app/Models/Blacklist.php` - 黑名单管理 + +### 6. 用户权限管理 +- **功能描述**: 基于角色的权限控制系统 +- **核心文件**: `app/Models/Admin.php`, `app/Models/Role.php`, `app/Models/Permission.php` +- **技术实现**: 使用 Spatie Laravel Permission 包 + +### 7. 多语言支持 +- **功能描述**: 支持多语言国际化 +- **实现方式**: 通过 `set.locale` 中间件实现 +- **语言文件**: `lang/` 目录下的语言包 + +## 技术架构 + +### 后端技术栈 +- **框架**: Laravel 9.x +- **PHP版本**: ^8.0.2 +- **数据库**: MySQL +- **认证**: Laravel Sanctum (JWT) +- **权限管理**: Spatie Laravel Permission +- **API文档**: Swagger (darkaonline/l5-swagger) + +### 核心依赖包 +```json +{ + "darkaonline/l5-swagger": "^8.6", // API文档生成 + "spatie/laravel-permission": "^5.5", // 权限管理 + "owen-it/laravel-auditing": "^13.6", // 操作审计 + "maatwebsite/excel": "^3.1", // Excel导入导出 + "overtrue/wechat": "~5.0", // 微信SDK + "lpilp/guomi": "^2.0", // 国密加密 + "overtrue/pinyin": "^5.0" // 拼音转换 +} +``` + +### API 路由结构 + +#### 门岗端接口 (`/api/gate/`) +- `GET /visits` - 获取访客列表 +- `POST /visits/detail` - 获取访客详情 +- `POST /visits/update` - 更新访客状态 +- `GET /visits/use-code` - 核销访客 + +#### 管理后台接口 (`/api/admin/`) +- `/visits/*` - 访客管理 +- `/studies/*` - 学习内容管理 +- `/study-asks/*` - 学习题目管理 +- `/visit-times/*` - 访问时间管理 +- `/configs/*` - 系统配置 +- `/blacklists/*` - 黑名单管理 +- `/visit-areas/*` - 访问区域管理 + +#### 移动端接口 (`/api/mobile/`) +- `/user/*` - 用户相关 +- `/visit/*` - 访客预约相关 + +## 数据库设计 + +### 核心数据表 +- `visits` - 访客预约记录表 +- `visit_audits` - 审核记录表 +- `visit_logs` - 访客操作日志表 +- `gate_logs` - 门岗操作日志表 +- `admins` - 管理员表 +- `visit_areas` - 访问区域表 +- `visit_times` - 访问时间段表 +- `studies` - 学习内容表 +- `study_asks` - 学习题目表 + +## 安全特性 + +### 认证与授权 +- 使用 Laravel Sanctum 进行 API 认证 +- 基于角色的权限控制 (RBAC) +- 支持多端认证 (admin/mobile) + +### 数据安全 +- 支持国密加密算法 (SM2) +- 操作审计日志记录 +- 软删除保护重要数据 + +### 加密命令 +项目提供了 SM2 加密/解密命令: +- `php artisan sm2:encrypt` - 加密数据 +- `php artisan sm2:decrypt` - 解密数据 + +## 部署要求 + +### 环境要求 +- PHP >= 8.0.2 +- MySQL >= 5.7 +- Composer +- Node.js (用于前端资源编译) + +### 安装步骤 +1. 克隆项目代码 +2. 运行 `composer install` 安装依赖 +3. 复制 `.env.example` 为 `.env` 并配置数据库 +4. 运行 `php artisan key:generate` 生成应用密钥 +5. 运行 `php artisan migrate` 执行数据库迁移 +6. 运行 `php artisan db:seed` 填充初始数据 +7. 配置 Web 服务器指向 `public` 目录 + +## API 文档 + +系统集成了 Swagger API 文档,可通过以下方式访问: +- 开发环境: `http://domain/api/documentation` +- 控制器: `app/Http/Controllers/SwaggerController.php` + +## 日志系统 + +### 访客操作日志 +- **表**: `visit_logs` +- **类型**: 进厂、离厂、审核等操作记录 + +### 门岗操作日志 +- **表**: `gate_logs` +- **功能**: 记录门岗的所有操作行为 + +### 系统审计日志 +- **实现**: Laravel Auditing 包 +- **功能**: 自动记录模型的增删改操作 + +## 国际化支持 + +系统支持多语言,通过中间件 `set.locale` 实现: +- 语言包位置: `lang/` 目录 +- 支持动态语言切换 +- API 接口均支持多语言响应 + +## 文件上传管理 + +- **控制器**: `app/Http/Controllers/Admin/UploadController.php` +- **模型**: `app/Models/Upload.php` +- **功能**: 支持图片、文档等文件上传和管理 + +## 开发建议 + +1. **代码规范**: 遵循 PSR-4 自动加载标准 +2. **API 设计**: 遵循 RESTful 设计规范 +3. **错误处理**: 使用统一的 API 响应格式 +4. **数据验证**: 使用 Laravel Validator 进行数据验证 +5. **日志记录**: 重要操作需要记录详细日志 + +## 联系信息 + +如需了解更多项目详情或技术支持,请联系开发团队。 \ No newline at end of file diff --git a/TESTING_SUMMARY.md b/TESTING_SUMMARY.md deleted file mode 100644 index 7a13d17..0000000 --- a/TESTING_SUMMARY.md +++ /dev/null @@ -1,128 +0,0 @@ -# 单元测试和接口测试总结 - -## 测试完成情况 - -### ✅ 已完成的测试 - -1. **单元测试 (Unit Tests)** - - VisitModelTest: 11个测试通过,3个跳过 - - 测试了模型的基本功能、常量定义、类型转换等 - -2. **功能测试 (Feature Tests)** - - SimpleApiTest: 6个测试通过,1个失败 - - 测试了API接口的基本功能 - -### ✅ 测试通过的功能 - -1. **门岗端接口** - - ✅ GET `/api/gate/visits` - 拜访列表接口 - - ✅ GET `/api/gate/visits/{id}` - 拜访详情接口 - - ✅ POST `/api/gate/visits/{id}/update` - 拜访更新接口 - -2. **多语言支持** - - ✅ 中文响应 (Accept-Language: zh-CN) - - ✅ 英文响应 (Accept-Language: en) - - ✅ 默认语言响应 - -3. **API响应格式** - - ✅ 统一的JSON响应格式 - - ✅ 包含code、message、data字段 - - ✅ 正确的数据类型 - -4. **路由验证** - - ✅ 所有门岗端路由可访问 - - ✅ 路由不存在404错误 - -### ⚠️ 需要关注的问题 - -1. **认证测试失败** - - 后台管理接口应该返回401未授权,但返回了200 - - 可能是中间件配置问题 - -## 测试覆盖范围 - -### 1. 模型测试 -- **Visit模型**: 类型转换、常量定义、关联关系 -- **文件字段**: 单文件和多文件字段的处理 -- **货车图片**: vehicle_images字段的JSON处理 - -### 2. API接口测试 -- **门岗端接口**: 无需认证的接口 -- **响应格式**: 统一的JSON响应结构 -- **错误处理**: 404、400等错误状态码 -- **多语言**: 中英文语言切换 - -### 3. 数据验证 -- **文件关联**: 单文件和多文件的关联关系 -- **类型转换**: JSON字段的正确处理 -- **数据完整性**: 模型属性的正确性 - -## 测试文件结构 - -``` -tests/ -├── Unit/ -│ └── Models/ -│ └── VisitModelTest.php # 模型单元测试 -├── Feature/ -│ ├── SimpleApiTest.php # 简化API测试 -│ ├── GateControllerTest.php # 门岗端控制器测试 -│ ├── AdminControllerTest.php # 后台管理控制器测试 -│ └── ApiRoutesTest.php # API路由测试 -└── database/factories/ - ├── VisitFactory.php # 拜访数据工厂 - ├── VisitLogFactory.php # 拜访日志工厂 - ├── StudyFactory.php # 学习数据工厂 - ├── BlacklistFactory.php # 黑名单工厂 - └── UploadFactory.php # 上传文件工厂 -``` - -## 运行测试命令 - -```bash -# 运行所有测试 -php artisan test - -# 运行单元测试 -php artisan test --testsuite=Unit - -# 运行功能测试 -php artisan test --testsuite=Feature - -# 运行特定测试文件 -php artisan test tests/Feature/SimpleApiTest.php - -# 运行特定测试方法 -php artisan test --filter=test_gate_visits_endpoint_exists -``` - -## 测试结果分析 - -### 成功案例 -1. **接口可访问性**: 所有门岗端接口都能正常访问 -2. **响应格式**: API返回统一的JSON格式 -3. **多语言支持**: 语言切换功能正常 -4. **模型功能**: 基本模型功能正常 - -### 需要改进的地方 -1. **认证中间件**: 后台管理接口的认证验证 -2. **数据工厂**: 需要为所有模型添加HasFactory trait -3. **完整测试**: 需要添加更多边界条件测试 - -## 建议 - -1. **修复认证问题**: 检查后台管理接口的中间件配置 -2. **完善测试覆盖**: 添加更多业务逻辑测试 -3. **性能测试**: 添加接口性能测试 -4. **集成测试**: 添加端到端的集成测试 - -## 总结 - -系统的基本功能测试已经完成,主要接口都能正常工作: -- ✅ 门岗端接口正常 -- ✅ 多语言支持正常 -- ✅ 文件字段处理正常 -- ✅ API响应格式统一 -- ⚠️ 后台认证需要检查 - -测试覆盖了核心功能,为系统的稳定运行提供了保障。 diff --git a/VEHICLE_IMAGES_IMPLEMENTATION.md b/VEHICLE_IMAGES_IMPLEMENTATION.md deleted file mode 100644 index e2b8c03..0000000 --- a/VEHICLE_IMAGES_IMPLEMENTATION.md +++ /dev/null @@ -1,176 +0,0 @@ -# 货车图片上传功能实现总结 - -## 实现内容 - -根据要求,已在拜访预约表(visits表)中增加了json字段来存储货车图片,并完善了相关的上传逻辑。 - -## 数据库变更 - -### 1. 新增字段 -- **字段名**: `vehicle_images` -- **类型**: `json` -- **说明**: 存储货车图片的uploads表id数组 -- **迁移文件**: `2025_09_09_112040_add_vehicle_images_to_visits_table.php` - -### 2. 字段设计 -```sql -ALTER TABLE visits ADD COLUMN vehicle_images JSON NULL COMMENT '货车图片id数组'; -``` - -## 模型更新 - -### Visit模型更新 -1. **类型转换**: 在`$casts`中添加了`'vehicle_images' => 'array'` -2. **关联关系**: 添加了`getVehicleImagesAttribute()`方法 - -```php -// 类型转换 -protected $casts = [ - 'vehicle_images' => 'array', - // ... 其他字段 -]; - -// 获取货车图片列表(多文件) -public function getVehicleImagesAttribute() -{ - if (!$this->vehicle_images) { - return []; - } - return Upload::whereIn('id', $this->vehicle_images)->get(); -} -``` - -## 控制器更新 - -### GateController更新 -1. **添加Upload模型引用** -2. **完善upload_vehicle操作逻辑** -3. **更新Swagger文档** - -```php -case 'upload_vehicle': - // 上传货车图片 - if ($visit->type != Visit::TYPE_LOGISTICS_CAR) { - return $this->error(__('gate.only_logistics_cars_can_upload_images')); - } - - $vehicleImages = $request->input('vehicle_images', []); - if (empty($vehicleImages)) { - return $this->error(__('gate.vehicle_images_required')); - } - - // 验证图片ID是否存在于uploads表中 - $existingImages = Upload::whereIn('id', $vehicleImages)->pluck('id')->toArray(); - if (count($existingImages) !== count($vehicleImages)) { - return $this->error(__('gate.invalid_vehicle_images')); - } - - // 存储货车图片ID数组到vehicle_images字段 - $visit->vehicle_images = $vehicleImages; - $visit->save(); - - // 记录日志 - $this->createVisitLog($visit, VisitLog::TYPE_ENTER, __('gate.upload_vehicle_images_log', ['count' => count($vehicleImages)])); - - break; -``` - -## API接口 - -### 上传货车图片接口 -- **路径**: `POST /api/gate/visits/{id}/update` -- **参数**: - ```json - { - "action": "upload_vehicle", - "vehicle_images": [123, 456, 789] - } - ``` - -### Swagger文档更新 -```php -@OA\Property(property="vehicle_images", type="array", description="货车图片ID数组(上传货车图片时必填)", @OA\Items(type="integer")) -``` - -## 多语言支持 - -### 中文翻译 (lang/zh-CN/gate.php) -```php -'invalid_vehicle_images' => '货车图片ID无效', -``` - -### 英文翻译 (lang/en/gate.php) -```php -'invalid_vehicle_images' => 'Invalid vehicle image IDs', -``` - -## 使用方式 - -### 1. 上传货车图片 -```php -// 通过API上传 -POST /api/gate/visits/123/update -{ - "action": "upload_vehicle", - "vehicle_images": [123, 456, 789] -} -``` - -### 2. 获取货车图片 -```php -// 获取拜访记录及货车图片 -$visit = Visit::with('acceptAdminSign')->find(123); -$vehicleImages = $visit->vehicle_images; // 返回Upload模型集合 -``` - -### 3. API返回数据格式 -```json -{ - "id": 123, - "name": "访客姓名", - "type": 3, - "vehicle_images": [ - { - "id": 123, - "original_name": "vehicle1.jpg", - "url": "http://domain.com/uploads/vehicle1.jpg" - }, - { - "id": 456, - "original_name": "vehicle2.jpg", - "url": "http://domain.com/uploads/vehicle2.jpg" - } - ] -} -``` - -## 验证逻辑 - -1. **类型验证**: 只有物流车辆(type=3)才能上传货车图片 -2. **必填验证**: vehicle_images参数不能为空 -3. **存在性验证**: 验证所有图片ID是否存在于uploads表中 -4. **数据存储**: 将验证通过的图片ID数组存储到vehicle_images字段 - -## 日志记录 - -上传货车图片时会自动记录操作日志: -- **日志类型**: VisitLog::TYPE_ENTER -- **日志内容**: "上传货车图片:{图片数量}张" - -## 注意事项 - -1. **文件存储**: 所有图片都存储在uploads表中,通过id关联 -2. **权限控制**: 只有物流车辆类型的拜访才能上传货车图片 -3. **数据验证**: 严格验证图片ID的有效性 -4. **日志记录**: 所有操作都有完整的日志记录 -5. **多语言**: 支持中英双语错误提示 - -## 测试建议 - -1. **正常上传**: 测试物流车辆上传多张货车图片 -2. **权限验证**: 测试非物流车辆尝试上传图片 -3. **数据验证**: 测试无效的图片ID -4. **空值处理**: 测试空的vehicle_images参数 -5. **多语言**: 测试中英文错误提示 - -货车图片上传功能已完整实现,符合所有要求! diff --git a/app/Console/Commands/Sm2DecryptCommand.php b/app/Console/Commands/Sm2DecryptCommand.php new file mode 100644 index 0000000..985c5d8 --- /dev/null +++ b/app/Console/Commands/Sm2DecryptCommand.php @@ -0,0 +1,46 @@ +doDecrypt($cipher, $privateKey, true, $mode); + + if ($plain === '') { + $this->error('解密失败:C3 校验不通过或格式不匹配'); + return self::FAILURE; + } + + $this->line($plain); + return self::SUCCESS; + } catch (\Throwable $e) { + $this->error('SM2 解密异常:' . $e->getMessage()); + return self::FAILURE; + } + } +} + + diff --git a/app/Console/Commands/Sm2EncryptCommand.php b/app/Console/Commands/Sm2EncryptCommand.php new file mode 100644 index 0000000..ae85637 --- /dev/null +++ b/app/Console/Commands/Sm2EncryptCommand.php @@ -0,0 +1,136 @@ +normalizePublicKeyToHex($publicKey); + + // RtSm2 缺省返回hex,且加密返回 c1c3c2 + $sm2 = new \Rtgm\sm\RtSm2('hex', true); + $cipherHex = $sm2->doEncrypt($message, $pubHex, $mode); + $this->line($cipherHex); + return self::SUCCESS; + } catch (\Throwable $e) { + $this->error('SM2 加密失败:' . $e->getMessage()); + return self::FAILURE; + } + } + + private function normalizePublicKeyToHex(string $key): string + { + $trimmed = trim($key); + // 如果已经是 128/130 长度的HEX,直接返回 + if (preg_match('/^(04)?[0-9a-fA-F]{128}$/', $trimmed)) { + return strtolower($trimmed); + } + + // 可能是 Base64(SPKI SubjectPublicKeyInfo) + $bin = base64_decode($trimmed, true); + if ($bin === false) { + throw new \InvalidArgumentException('公钥格式不支持:既不是HEX也不是Base64'); + } + + // 优先使用 OpenSSL 解析 SPKI,获取未压缩公钥点 + if (function_exists('openssl_pkey_get_public')) { + $pem = "-----BEGIN PUBLIC KEY-----\n" . chunk_split($trimmed, 64, "\n") . "-----END PUBLIC KEY-----\n"; + $res = @openssl_pkey_get_public($pem); + if ($res !== false) { + $details = openssl_pkey_get_details($res); + if ($details && isset($details['ec']) && isset($details['ec']['public_key'])) { + $pubBin = $details['ec']['public_key']; // binary starting with 0x04 + if ($pubBin !== '' && ord($pubBin[0]) === 0x04) { + return strtolower(bin2hex($pubBin)); + } + } + // 某些 PHP 版本可能在 key 索引中提供公钥 + if ($details && isset($details['key'])) { + // 再次从 details['key'](PEM)中提取 BIT STRING + $pem2 = $details['key']; + $clean = str_replace(["-----BEGIN PUBLIC KEY-----", "-----END PUBLIC KEY-----", "\r", "\n", " "], '', $pem2); + $bin2 = base64_decode($clean, true); + if ($bin2 !== false) { + $found = $this->extractUncompressedPointFromSpki($bin2); + if ($found !== null) { + return $found; + } + } + } + } + } + + // 退而求其次:直接从 SPKI 二进制中提取 BIT STRING(不下钻根 SEQUENCE,直接扫描 0x03 标签) + $found = $this->extractUncompressedPointFromSpki($bin); + if ($found !== null) { + return $found; + } + + throw new \RuntimeException('未找到公钥BIT STRING'); + } + + private function extractUncompressedPointFromSpki(string $spkiBin): ?string + { + $len = strlen($spkiBin); + for ($pos = 0; $pos < $len; $pos++) { + if (ord($spkiBin[$pos]) !== 0x03) { + continue; + } + $p = $pos + 1; + if ($p >= $len) + break; + $lenByte = ord($spkiBin[$p++]); + if (($lenByte & 0x80) === 0) { + $l = $lenByte; + } else { + $num = $lenByte & 0x7F; + if ($num === 0 || $p + $num > $len) { + continue; + } + $l = 0; + for ($i = 0; $i < $num; $i++) { + $l = ($l << 8) | ord($spkiBin[$p++]); + } + } + if ($p + $l > $len) { + continue; + } + if ($l < 2) { + continue; + } + $unusedBits = ord($spkiBin[$p]); + if ($unusedBits !== 0) { + continue; + } + $bitString = substr($spkiBin, $p + 1, $l - 1); + if ($bitString !== '' && ord($bitString[0]) === 0x04) { + return strtolower(bin2hex($bitString)); + } + } + return null; + } +} + + diff --git a/app/Http/Controllers/Admin/StudyController.php b/app/Http/Controllers/Admin/StudyController.php index e1366d2..7553f4a 100644 --- a/app/Http/Controllers/Admin/StudyController.php +++ b/app/Http/Controllers/Admin/StudyController.php @@ -6,6 +6,7 @@ use App\Http\Requests\StudyRequest; use App\Models\Study; use App\Models\StudyAsk; use App\Helpers\ResponseCode; +use App\Models\StudyLog; use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; @@ -22,6 +23,36 @@ class StudyController extends BaseController parent::__construct(new Study()); } + /** + * @OA\Get( + * path="/api/admin/studies/study-log", + * tags={"学习内容管理"}, + * summary="学习记录", + * description="", + * @OA\Parameter(name="keyword", in="query", @OA\Schema(type="string"), required=false, description="关键词"), + * @OA\Parameter(name="page_size", in="query", @OA\Schema(type="string"), required=false, description="每页显示的条数"), + * @OA\Parameter(name="page", in="query", @OA\Schema(type="string"), required=false, description="页码"), + * @OA\Parameter(name="sort_name", in="query", @OA\Schema(type="string"), required=false, description="排序字段名字"), + * @OA\Parameter(name="sort_type", in="query", @OA\Schema(type="string"), required=false, description="排序类型"), + * @OA\Parameter(name="token", in="query", @OA\Schema(type="string"), required=true, description="token"), + * @OA\Response( + * response="200", + * description="暂无" + * ) + * ) + */ + public function studyLog() + { + $all = request()->all(); + $list = StudyLog::with('user')->where(function ($query) use ($all) { + if (isset($all['keyword'])) { + $query->where('content', 'like', '%' . $all['keyword'] . '%'); + } + })->orderBy($all['sort_name'] ?? 'id', $all['sort_type'] ?? 'desc') + ->paginate($all['page_size'] ?? 20); + return $this->success($list); + } + /** * @OA\Get( * path="/api/admin/studies/index", @@ -197,4 +228,4 @@ class StudyController extends BaseController } return $query; } -} \ No newline at end of file +} diff --git a/composer.json b/composer.json index 526cb3a..1530ee9 100644 --- a/composer.json +++ b/composer.json @@ -14,6 +14,7 @@ "laravel/framework": "^9.2", "laravel/sanctum": "^2.15", "laravel/tinker": "^2.7", + "lpilp/guomi": "^2.0", "maatwebsite/excel": "^3.1", "overtrue/pinyin": "^5.0", "overtrue/wechat": "~5.0", diff --git a/routes/api.php b/routes/api.php index aa86440..f93e977 100644 --- a/routes/api.php +++ b/routes/api.php @@ -43,6 +43,7 @@ Route::group(['prefix' => 'admin', 'middleware' => ['sanctum.jwt:admin', 'set.lo // 学习内容管理路由 Route::prefix('studies')->group(function () { + Route::get("study-log", [\App\Http\Controllers\Admin\StudyController::class, "studyLog"]); Route::get('index', [\App\Http\Controllers\Admin\StudyController::class, 'index']); Route::get('show', [\App\Http\Controllers\Admin\StudyController::class, 'show']); Route::post('save', [\App\Http\Controllers\Admin\StudyController::class, 'save']); diff --git a/test_api_endpoints.php b/test_api_endpoints.php deleted file mode 100644 index b81c729..0000000 --- a/test_api_endpoints.php +++ /dev/null @@ -1,87 +0,0 @@ - [ - 'header' => 'Accept-Language: zh-CN' - ] -]); -$url = 'http://localhost:8000/api/gate/visits'; -$response = @file_get_contents($url, false, $context); -if ($response !== false) { - echo "✅ 中文语言支持正常\n"; -} else { - echo "❌ 中文语言支持异常\n"; -} - -// 测试英文响应 -$context = stream_context_create([ - 'http' => [ - 'header' => 'Accept-Language: en' - ] -]); -$response = @file_get_contents($url, false, $context); -if ($response !== false) { - echo "✅ 英文语言支持正常\n"; -} else { - echo "❌ 英文语言支持异常\n"; -} - -// 测试后台管理接口 -echo "\n3. 测试后台管理接口:\n"; - -$url = 'http://localhost:8000/api/admin/visits/index'; -$response = @file_get_contents($url); -if ($response !== false) { - $data = json_decode($response, true); - if (isset($data['code']) && $data['code'] == 401) { - echo "✅ GET /api/admin/visits/index - 认证保护正常\n"; - } else { - echo "⚠️ GET /api/admin/visits/index - 认证保护可能有问题\n"; - } -} else { - echo "❌ GET /api/admin/visits/index - 接口无法访问\n"; -} - -echo "\n=== 测试完成 ===\n"; -echo "注意: 此测试需要Laravel开发服务器运行在 localhost:8000\n"; -echo "启动命令: php artisan serve\n";