master
cody 11 months ago
parent e61dee61ef
commit 491dd67bc7

@ -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字段
- ✅ 创建专门的邮件通知类
- ✅ 支持中英双语邮件内容
- ✅ 完整的测试覆盖
- ✅ 保持原有审核流程不变
现在当用户提交拜访预约时,系统会自动向被访管理员发送包含详细信息的邮件通知,提供更好的用户体验。

@ -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()` 调用,确保整个系统使用统一的错误响应格式。所有门岗端接口测试通过,系统响应格式完全统一。

@ -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. **软删除**:文件删除时使用软删除,保持数据完整性
所有文件字段已按照要求修正完成,可以正常使用!

@ -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. **标准化**: 符合系统架构规范
## 总结
门岗端控制器已成功更新为使用系统统一的响应方法,所有接口都能正常工作,响应格式与系统其他部分保持一致。测试显示主要功能正常,只有认证相关的测试需要进一步检查。

@ -1,206 +0,0 @@
# 前台Mobile端多语言支持实现总结
## 实现内容
已成功为前台mobile端所有接口添加中英双语支持确保所有错误消息和响应文本都能根据 `Accept-Language` 头部自动切换语言。
## 修改的文件
### 1. 语言文件
#### 中文语言文件 (`lang/zh-CN/mobile.php`)
```php
<?php
return [
// 用户管理
'user' => [
'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
<?php
return [
// User Management
'user' => [
'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` 头部自动切换语言,提供更好的国际化用户体验。

@ -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` 数组

@ -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. **日志记录**: 重要操作需要记录详细日志
## 联系信息
如需了解更多项目详情或技术支持,请联系开发团队。

@ -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响应格式统一
- ⚠️ 后台认证需要检查
测试覆盖了核心功能,为系统的稳定运行提供了保障。

@ -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. **多语言**: 测试中英文错误提示
货车图片上传功能已完整实现,符合所有要求!

@ -0,0 +1,46 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
class Sm2DecryptCommand extends Command
{
/**
* The name and signature of the console command.
*
* 用法php artisan sm2:decrypt "密文hex" "私钥hex" [--mode=c1c3c2] [--asn1]
*/
protected $signature = 'sm2:decrypt';
/**
* The console command description.
*/
protected $description = '使用 SM2 私钥解密(硬编码密文与私钥),输出明文';
public function handle(): int
{
// 硬编码密文与私钥(与加密命令匹配)
$cipher = 'e27c3780e7069bda7082a23a489d77587ce309583ed99253f66e1d9833ed1a1d0b5ce86dc6714e9974cf258589139d7b1855e8c9fa2f2c1175ee123a95a23e9bbbaf04ad750b5c304a875aab17851fac60453940aac9da33cc4249a52ee46be446cf5083b5';
$privateKey = 'MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQgrEdb4ONfRO2N0cXZsDY0PcGpfzQKfrg6DB0r+1dt4EmgCgYIKoEcz1UBgi2hRANCAARxd7Iuetxpc/2pgiGY1aLz6RvYyz696c9B8s4FghM0K5T4G+ywxIQ4pNFRe2sbp3wJydCIX647Xw1OCRl/vMHt';
$mode = 1; // C1C3C2
try {
$sm2 = new \Rtgm\sm\RtSm2('hex', true);
$plain = $sm2->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;
}
}
}

@ -0,0 +1,136 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
class Sm2EncryptCommand extends Command
{
/**
* The name and signature of the console command.
*
* 用法php artisan sm2:encrypt "明文" "公钥hex(可含04前缀)" --mode=c1c3c2
*/
protected $signature = 'sm2:encrypt';
/**
* The console command description.
*/
protected $description = '使用 SM2 公钥加密硬编码明文与公钥返回hex密文';
public function handle(): int
{
// 硬编码待加密明文与公钥hex含04前缀
$message = '{"title": "","district": "","xmlxS":"足球"}';
$publicKey = 'MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEkecuYCCoPprFvmgZoXTtEXcpYyvJi9rgizj+FMqVIL0OSQ24plc4F8ONugktvjPRbwvRly12ieRK9SGiG+9sjA==';
$mode = 1; // C1C3C2
try {
// 若提供的是 SPKI Base64 公钥转换为未压缩点HEX(04+x+y)
$pubHex = $this->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;
}
}

@ -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;
}
}
}

@ -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",

@ -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']);

@ -1,87 +0,0 @@
<?php
// API接口测试脚本
echo "=== 访客系统API接口测试 ===\n\n";
// 测试门岗端接口
echo "1. 测试门岗端接口:\n";
// 测试拜访列表接口
$url = 'http://localhost:8000/api/gate/visits';
$response = @file_get_contents($url);
if ($response !== false) {
$data = json_decode($response, true);
if (isset($data['code']) && isset($data['message']) && isset($data['data'])) {
echo "✅ GET /api/gate/visits - 拜访列表接口正常\n";
echo " 响应格式: code={$data['code']}, message={$data['message']}\n";
} else {
echo "❌ GET /api/gate/visits - 响应格式不正确\n";
}
} else {
echo "❌ GET /api/gate/visits - 接口无法访问\n";
}
// 测试拜访详情接口
$url = 'http://localhost:8000/api/gate/visits/1';
$response = @file_get_contents($url);
if ($response !== false) {
$data = json_decode($response, true);
if (isset($data['code']) && isset($data['message'])) {
echo "✅ GET /api/gate/visits/{id} - 拜访详情接口正常\n";
echo " 响应格式: code={$data['code']}, message={$data['message']}\n";
} else {
echo "❌ GET /api/gate/visits/{id} - 响应格式不正确\n";
}
} else {
echo "❌ GET /api/gate/visits/{id} - 接口无法访问\n";
}
// 测试多语言支持
echo "\n2. 测试多语言支持:\n";
// 测试中文响应
$context = stream_context_create([
'http' => [
'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";
Loading…
Cancel
Save