Files
MES/app/controllers/MiniappController.php
T
2026-08-08 18:28:49 +08:00

1034 lines
37 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace app\controllers;
use core\base\Controller;
use app\models\Employee;
use app\models\Workstation;
use app\models\StationDefinition;
use app\models\ProductModel;
use app\models\Customer;
use app\models\EmployeePermission;
/**
* 微信小程序 API 控制器
*
* 为 MES2 系统提供小程序端 JSON API,复用现有 Model 层
*
* 认证方式:Token (Bearer) — 登录后返回 token,后续请求通过 Authorization 头携带
* Token 存储在 DGZXY_miniapp_token 表中
*
* 响应格式:{ code: 0|1, message: "...", data: {...} }
* code=1 成功,code=0 失败
*/
class MiniappController extends Controller
{
// ==================== 构造函数 & CORS ====================
/**
* 覆盖基类构造函数:设置 CORS 和 JSON 响应头
*/
public function __construct($controller, $action)
{
parent::__construct($controller, $action);
header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Headers: Content-Type, Authorization');
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
// 处理 OPTIONS 预检请求
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(204);
exit;
}
}
// ==================== 工具方法 ====================
/**
* 成功响应
*/
private function success($data = [], $message = 'ok')
{
echo json_encode([
'code' => 1,
'message' => $message,
'data' => $data,
], JSON_UNESCAPED_UNICODE);
exit;
}
/**
* 失败响应
*/
private function fail($message, $code = 0, $data = [])
{
echo json_encode([
'code' => $code,
'message' => $message,
'data' => $data,
], JSON_UNESCAPED_UNICODE);
exit;
}
/**
* 获取 JSON 请求体
*/
private function getJsonInput()
{
$input = file_get_contents('php://input');
$data = json_decode($input, true);
return is_array($data) ? $data : [];
}
/**
* Token 认证 — 验证请求中的 Bearer Token
* @return array 用户信息 {id, emp_no, emp_name, role}
*/
protected function tokenAuth()
{
$token = '';
$authHeader = $_SERVER['HTTP_AUTHORIZATION'] ?? $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ?? '';
if (preg_match('/^Bearer\s+(.+)$/i', $authHeader, $matches)) {
$token = $matches[1];
}
if (empty($token)) {
$this->fail('未登录,请先登录');
}
try {
$pdo = \core\db\Db::pdo();
$stmt = $pdo->prepare(
"SELECT t.*, e.emp_name, e.role
FROM DGZXY_miniapp_token t
JOIN DGZXY_employee e ON t.employee_id = e.id
WHERE t.token = :token AND t.expires_at > NOW() AND t.status = 1"
);
$stmt->execute([':token' => $token]);
$tokenInfo = $stmt->fetch(\PDO::FETCH_ASSOC);
if (!$tokenInfo) {
$this->fail('Token无效或已过期,请重新登录', 401);
}
return [
'id' => (int)$tokenInfo['employee_id'],
'emp_no' => $tokenInfo['emp_no'],
'emp_name' => $tokenInfo['emp_name'],
'role' => $tokenInfo['role'],
];
} catch (\Throwable $e) {
$this->fail('认证服务异常');
}
}
/**
* 生成 Token(登录成功后调用)
*/
private function generateToken($employeeId, $empNo)
{
$token = bin2hex(random_bytes(32));
$expiresAt = date('Y-m-d H:i:s', time() + 7 * 24 * 3600); // 7天有效期
try {
$pdo = \core\db\Db::pdo();
// 使旧 token 失效
$pdo->prepare("UPDATE DGZXY_miniapp_token SET status = 0 WHERE employee_id = :eid")
->execute([':eid' => $employeeId]);
// 插入新 token
$pdo->prepare(
"INSERT INTO DGZXY_miniapp_token (token, employee_id, emp_no, expires_at, status, created_at)
VALUES (:token, :eid, :emp_no, :expires, 1, NOW())"
)->execute([
':token' => $token,
':eid' => $employeeId,
':emp_no' => $empNo,
':expires' => $expiresAt,
]);
} catch (\Throwable $e) {
// Token 表不存在时创建
if (strpos($e->getMessage(), 'exist') !== false || strpos($e->getMessage(), 'not found') !== false) {
$this->createTokenTable();
return $this->generateToken($employeeId, $empNo);
}
}
return [
'token' => $token,
'expires_at' => $expiresAt,
];
}
/**
* 创建 Token 表(首次运行时)
*/
private function createTokenTable()
{
$pdo = \core\db\Db::pdo();
$pdo->exec("
CREATE TABLE IF NOT EXISTS DGZXY_miniapp_token (
id INT AUTO_INCREMENT PRIMARY KEY,
token VARCHAR(128) NOT NULL,
employee_id INT NOT NULL,
emp_no VARCHAR(50) NOT NULL,
expires_at DATETIME NOT NULL,
status TINYINT DEFAULT 1,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_token (token),
INDEX idx_employee (employee_id),
INDEX idx_expires (expires_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
");
}
/**
* 检查工位权限
*/
private function checkStationPermission($user, $stationType)
{
if ($user['role'] === Controller::ROLE_SUPER_ADMIN || $user['role'] === Controller::ROLE_ADMIN) {
return true;
}
$permModel = new EmployeePermission();
return $permModel->canAccess($user['id'], $user['role'], $stationType);
}
/**
* 获取工位字段配置
*/
private function getStationFieldConfig($stationType)
{
$ws = new Workstation();
$station = $ws->getByType($stationType);
if ($station && !empty($station['fields_config'])) {
return parseFieldsConfig($stationType, $station['fields_config']);
}
return getDefaultFieldsConfig($stationType);
}
/**
* 获取用户可访问的工位列表
*/
private function getAllowedStations($user)
{
$workstation = new Workstation();
$allStations = $workstation->getAll();
if ($user['role'] === Controller::ROLE_SUPER_ADMIN || $user['role'] === Controller::ROLE_ADMIN) {
return $allStations;
}
$permModel = new EmployeePermission();
$allowedTypes = $permModel->getAllowedStations($user['id']);
if (empty($allowedTypes)) {
return $allStations;
}
return array_values(array_filter($allStations, function($s) use ($allowedTypes) {
return in_array($s['station_type'], $allowedTypes);
}));
}
// ==================== 1. 登录 ====================
/**
* POST /MiniApp/login
* 请求: { emp_no: "员工编号", password: "密码" }
* 响应: { code:1, data: { token, expires_at, user: { id, emp_no, emp_name, role } } }
*/
public function login()
{
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
$this->fail('请使用 POST 请求');
}
$data = $this->getJsonInput();
$empNo = trim($data['emp_no'] ?? '');
$password = $data['password'] ?? '';
if (empty($empNo) || empty($password)) {
$this->fail('员工编号和密码不能为空');
}
// 暴力破解防护(基于数据库记录)
try {
$pdo = \core\db\Db::pdo();
// 检查最近 15 分钟内的失败次数
$stmt = $pdo->prepare(
"SELECT COUNT(*) as cnt FROM DGZXY_miniapp_login_log
WHERE emp_no = :eno AND success = 0 AND created_at > DATE_SUB(NOW(), INTERVAL 15 MINUTE)"
);
$stmt->execute([':eno' => $empNo]);
$failCount = $stmt->fetch(\PDO::FETCH_ASSOC)['cnt'] ?? 0;
if ($failCount >= 5) {
$this->fail('登录尝试次数过多,请15分钟后再试');
}
} catch (\Throwable $e) {
// 日志表不存在时忽略
}
$employee = new Employee();
$user = $employee->validateLogin($empNo, $password);
if (!$user) {
// 记录失败日志
try {
$pdo = \core\db\Db::pdo();
$pdo->prepare(
"INSERT INTO DGZXY_miniapp_login_log (emp_no, success, ip, created_at)
VALUES (:eno, 0, :ip, NOW())"
)->execute([':eno' => $empNo, ':ip' => $_SERVER['REMOTE_ADDR'] ?? '']);
} catch (\Throwable $e) {}
$this->fail('员工编号或密码错误');
}
// 生成 Token
$tokenInfo = $this->generateToken($user['id'], $user['emp_no']);
// 记录成功日志
try {
$pdo = \core\db\Db::pdo();
$pdo->prepare(
"INSERT INTO DGZXY_miniapp_login_log (emp_no, success, ip, created_at)
VALUES (:eno, 1, :ip, NOW())"
)->execute([':eno' => $empNo, ':ip' => $_SERVER['REMOTE_ADDR'] ?? '']);
} catch (\Throwable $e) {}
$this->success([
'token' => $tokenInfo['token'],
'expires_at' => $tokenInfo['expires_at'],
'user' => [
'id' => (int)$user['id'],
'emp_no' => $user['emp_no'],
'emp_name' => $user['emp_name'],
'role' => $user['role'],
],
], '登录成功');
}
// ==================== 2. 退出登录 ====================
/**
* POST /MiniApp/logout
* Headers: Authorization: Bearer <token>
*/
public function logout()
{
$authHeader = $_SERVER['HTTP_AUTHORIZATION'] ?? $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ?? '';
$token = '';
if (preg_match('/^Bearer\s+(.+)$/i', $authHeader, $matches)) {
$token = $matches[1];
}
if (!empty($token)) {
try {
$pdo = \core\db\Db::pdo();
$pdo->prepare("UPDATE DGZXY_miniapp_token SET status = 0 WHERE token = :t")
->execute([':t' => $token]);
} catch (\Throwable $e) {}
}
$this->success([], '已退出登录');
}
// ==================== 3. 获取用户信息 ====================
/**
* GET /MiniApp/userInfo
* Headers: Authorization: Bearer <token>
*/
public function userInfo()
{
$user = $this->tokenAuth();
$this->success(['user' => $user]);
}
// ==================== 4. 获取工位列表 ====================
/**
* GET /MiniApp/stations
* Headers: Authorization: Bearer <token>
* 响应: { stations: [{id, station_name, station_type, station_code, icon, sort_order}] }
*/
public function stations()
{
$user = $this->tokenAuth();
$stations = $this->getAllowedStations($user);
// 只返回小程序需要的字段
$result = array_map(function($s) {
return [
'id' => (int)$s['id'],
'station_name' => $s['station_name'] ?? '',
'station_type' => $s['station_type'] ?? '',
'station_code' => $s['station_code'] ?? '',
'icon' => $s['icon'] ?? 'cube',
'sort_order' => (int)($s['sort_order'] ?? 0),
];
}, $stations);
$this->success(['stations' => $result]);
}
// ==================== 5. 获取工位字段配置 ====================
/**
* GET /MiniApp/stationConfig?type=inbound
* Headers: Authorization: Bearer <token>
* 响应: { station_type, fields: [...], data_sources: { types, models, customers } }
*/
public function stationConfig()
{
$user = $this->tokenAuth();
$stationType = trim($_GET['type'] ?? '');
if (empty($stationType)) {
$this->fail('缺少工位类型参数 type');
}
// 权限检查
if (!$this->checkStationPermission($user, $stationType)) {
$this->fail('无权限访问该工位', 403);
}
// 获取字段配置
$rawFieldConfigs = $this->getStationFieldConfig($stationType);
$fieldMeta = $rawFieldConfigs['_meta'] ?? [];
unset($rawFieldConfigs['_meta']);
$fieldConfigs = array_values($rawFieldConfigs);
// 获取数据源
$productModel = new ProductModel();
$customer = new Customer();
// 从 fields_config._meta.model_type_filter 读取型号筛选类型
$defaultProductType = $fieldMeta['model_type_filter'] ?? '';
// 向后兼容:bizConfig 硬编码兜底
if (empty($defaultProductType)) {
if ($stationType === 'inbound') $defaultProductType = '半成品';
if ($stationType === 'warehouse') $defaultProductType = '成品';
}
$models = !empty($defaultProductType)
? $productModel->getByProductType($defaultProductType)
: $productModel->getAll();
$dataSources = [
'types' => $productModel->getProductTypes(),
'models' => $models,
'customers' => ($stationType === 'delivery') ? $customer->getAll() : [],
];
$this->success([
'station_type' => $stationType,
'fields' => $fieldConfigs,
'data_sources' => $dataSources,
]);
}
// ==================== 6. 获取型号列表(按产品类型筛选) ====================
/**
* GET /MiniApp/models?product_type=半成品
* Headers: Authorization: Bearer <token>
*/
public function models()
{
$this->tokenAuth();
$productType = trim($_GET['product_type'] ?? '');
$productModel = new ProductModel();
if ($productType === '') {
$models = $productModel->getAll();
} else {
$models = $productModel->getByProductType($productType);
}
$this->success(['models' => $models]);
}
// ==================== 7. 半成品入库 ====================
/**
* POST /MiniApp/inbound
* Headers: Authorization: Bearer <token>
* 请求: { product_type, product_model, serial_no }
*/
public function inbound()
{
$user = $this->tokenAuth();
if (!$this->checkStationPermission($user, 'inbound')) {
$this->fail('无权限操作该工位', 403);
}
$data = $this->getJsonInput();
// 必填校验
$errors = [];
if (empty($data['product_type'])) $errors[] = '产品类型不能为空';
if (empty($data['product_model'])) $errors[] = '产品型号不能为空';
if (empty($data['serial_no'])) $errors[] = '产品序列号不能为空';
if (!empty($errors)) {
$this->fail(implode('', $errors));
}
// 检查序列号是否已存在
$stationDef = new StationDefinition();
$existing = $stationDef->findByField('inbound', 'serial_no', $data['serial_no']);
if ($existing) {
$this->fail('该序列号已入库');
}
$record = [
'product_type' => $data['product_type'],
'product_model' => $data['product_model'],
'serial_no' => $data['serial_no'],
'test_status' => 'pending',
'operator' => $user['emp_name'],
];
$result = $stationDef->addRecord('inbound', $record);
if (!$result) {
$this->fail('入库失败');
}
$this->success(['id' => $id], '入库成功');
}
// ==================== 8. PCB 测试 ====================
/**
* POST /MiniApp/pcbTest
* Headers: Authorization: Bearer <token>
* 请求: { product_type, product_model, serial_no, static_power?, input_output?, test_status? }
*/
public function pcbTest()
{
$user = $this->tokenAuth();
if (!$this->checkStationPermission($user, 'pcb_test')) {
$this->fail('无权限操作该工位', 403);
}
$data = $this->getJsonInput();
if (empty($data['serial_no'])) {
$this->fail('PCB序列号不能为空');
}
$stationDef = new StationDefinition();
$record = [
'product_type' => $data['product_type'] ?? '',
'product_model' => $data['product_model'] ?? '',
'serial_no' => $data['serial_no'],
'static_power' => isset($data['static_power']) && $data['static_power'] !== '' ? $data['static_power'] : null,
'input_output' => $data['input_output'] ?? null,
'test_status' => $data['test_status'] ?? null,
'operator' => $user['emp_name'],
];
$result = $stationDef->addRecord('pcb_test', $record);
if (!$result) {
$this->fail('提交失败');
}
$this->success([], 'PCB测试数据提交成功');
}
// ==================== 9. 电池状态 ====================
/**
* POST /MiniApp/battery
* Headers: Authorization: Bearer <token>
* 请求: { serial_no, voltage_platform?, no_load_voltage?, internal_resistance? }
*/
public function battery()
{
$user = $this->tokenAuth();
if (!$this->checkStationPermission($user, 'battery')) {
$this->fail('无权限操作该工位', 403);
}
$data = $this->getJsonInput();
if (empty($data['serial_no'])) {
$this->fail('产品序列号不能为空');
}
$stationDef = new StationDefinition();
$record = [
'serial_no' => $data['serial_no'],
'voltage_platform' => isset($data['voltage_platform']) && $data['voltage_platform'] !== '' ? $data['voltage_platform'] : null,
'no_load_voltage' => isset($data['no_load_voltage']) && $data['no_load_voltage'] !== '' ? $data['no_load_voltage'] : null,
'internal_resistance' => isset($data['internal_resistance']) && $data['internal_resistance'] !== '' ? $data['internal_resistance'] : null,
'operator' => $user['emp_name'],
];
$result = $stationDef->addRecord('battery', $record);
if (!$result) {
$this->fail('提交失败');
}
$this->success([], '电池数据提交成功');
}
// ==================== 10. 产品组装(三码关联) ====================
/**
* POST /MiniApp/assembly
* Headers: Authorization: Bearer <token>
* 请求: { product_model, finished_serial, battery_serial, pcb_serial }
*/
public function assembly()
{
$user = $this->tokenAuth();
if (!$this->checkStationPermission($user, 'assembly')) {
$this->fail('无权限操作该工位', 403);
}
$data = $this->getJsonInput();
$errors = [];
if (empty($data['finished_serial'])) $errors[] = '成品序列号不能为空';
if (empty($data['battery_serial'])) $errors[] = '电池序列号不能为空';
if (empty($data['pcb_serial'])) $errors[] = 'PCB序列号不能为空';
if (empty($data['product_model'])) $errors[] = '产品型号不能为空';
if (!empty($errors)) {
$this->fail(implode('', $errors));
}
$stationDef = new StationDefinition();
// 检查成品序列号是否已组装
$existing = $stationDef->findByField('assembly', 'finished_serial', $data['finished_serial']);
if ($existing) {
$this->fail('该成品序列号(' . $data['finished_serial'] . ')已组装,不能重复提交');
}
$record = [
'finished_serial' => $data['finished_serial'],
'battery_serial' => $data['battery_serial'],
'pcb_serial' => $data['pcb_serial'],
'product_model' => $data['product_model'],
'operator' => $user['emp_name'],
];
$result = $stationDef->addRecord('assembly', $record);
if (!$result) {
$this->fail('组装记录提交失败');
}
$this->success([], '组装记录提交成功');
}
// ==================== 11. 成品测试 ====================
/**
* POST /MiniApp/finishedTest
* Headers: Authorization: Bearer <token>
* 请求: { finished_serial, test_item, test_result, test_steps?, product_model? }
*/
public function finishedTest()
{
$user = $this->tokenAuth();
if (!$this->checkStationPermission($user, 'finished_test')) {
$this->fail('无权限操作该工位', 403);
}
$data = $this->getJsonInput();
if (empty($data['finished_serial'])) $this->fail('成品序列号不能为空');
if (empty($data['test_item'])) $this->fail('测试项目不能为空');
if (empty($data['test_result'])) $this->fail('测试结果不能为空');
$stationDef = new StationDefinition();
$record = [
'finished_serial' => $data['finished_serial'],
'test_item' => $data['test_item'],
'test_result' => $data['test_result'],
'test_steps' => $data['test_steps'] ?? '',
'product_model' => $data['product_model'] ?? '',
'operator' => $user['emp_name'],
];
$result = $stationDef->addRecord('finished_test', $record);
if (!$result) {
$this->fail('提交失败');
}
$this->success([], '测试数据提交成功');
}
// ==================== 12. 成品入库 ====================
/**
* POST /MiniApp/warehouse
* Headers: Authorization: Bearer <token>
* 请求: { finished_serial, product_model }
*/
public function warehouse()
{
$user = $this->tokenAuth();
if (!$this->checkStationPermission($user, 'warehouse')) {
$this->fail('无权限操作该工位', 403);
}
$data = $this->getJsonInput();
if (empty($data['finished_serial'])) $this->fail('成品序列号不能为空');
$stationDef = new StationDefinition();
$record = [
'finished_serial' => $data['finished_serial'],
'product_model' => $data['product_model'] ?? '',
'operator' => $user['emp_name'],
];
$result = $stationDef->addRecord('warehouse', $record);
if (!$result) {
$this->fail('入库失败');
}
$this->success([], '入库成功');
}
// ==================== 13. 出货登记 ====================
/**
* POST /MiniApp/delivery
* Headers: Authorization: Bearer <token>
* 请求: { finished_serial, customer_name, product_model }
*/
public function delivery()
{
$user = $this->tokenAuth();
if (!$this->checkStationPermission($user, 'delivery')) {
$this->fail('无权限操作该工位', 403);
}
$data = $this->getJsonInput();
if (empty($data['finished_serial'])) $this->fail('成品序列号不能为空');
if (empty($data['customer_name'])) $this->fail('客户名称不能为空');
$stationDef = new StationDefinition();
$record = [
'finished_serial' => $data['finished_serial'],
'customer_name' => $data['customer_name'],
'product_model' => $data['product_model'] ?? '',
'operator' => $user['emp_name'],
];
$result = $stationDef->addRecord('delivery', $record);
if (!$result) {
$this->fail('出货登记失败');
}
$this->success([], '出货登记成功');
}
// ==================== 14. 工位记录查询 ====================
/**
* GET /MiniApp/records?type=inbound&page=1&limit=20
* Headers: Authorization: Bearer <token>
*/
public function records()
{
$user = $this->tokenAuth();
$stationType = trim($_GET['type'] ?? '');
$page = max(1, intval($_GET['page'] ?? 1));
$limit = min(100, max(1, intval($_GET['limit'] ?? 20)));
$offset = ($page - 1) * $limit;
if (empty($stationType)) {
$this->fail('缺少工位类型参数 type');
}
if (!$this->checkStationPermission($user, $stationType)) {
$this->fail('无权限查看该工位记录', 403);
}
// 获取字段配置(用于表头信息)
$rawFieldConfigs = $this->getStationFieldConfig($stationType);
unset($rawFieldConfigs['_meta']);
$fieldConfigs = array_values($rawFieldConfigs);
try {
$pdo = \core\db\Db::pdo();
$tableMap = [
'inbound' => 'station_inbound',
'pcb_test' => 'pcb_test',
'battery' => 'station_battery',
'assembly' => 'product_assembly',
'finished_test' => 'station_finished_test',
'warehouse' => 'station_warehouse',
'delivery' => 'station_delivery',
];
$table = $tableMap[$stationType] ?? null;
if (!$table) {
$this->fail('无效的工位类型');
}
$fullTable = 'DGZXY_' . $table;
// 总数
$countSql = "SELECT COUNT(*) as total FROM {$fullTable}";
$countStmt = $pdo->query($countSql);
$total = $countStmt->fetch(\PDO::FETCH_ASSOC)['total'] ?? 0;
// 分页查询
$stmt = $pdo->prepare("SELECT * FROM {$fullTable} ORDER BY id DESC LIMIT :limit OFFSET :offset");
$stmt->bindValue(':limit', $limit, \PDO::PARAM_INT);
$stmt->bindValue(':offset', $offset, \PDO::PARAM_INT);
$stmt->execute();
$records = $stmt->fetchAll(\PDO::FETCH_ASSOC);
} catch (\Throwable $e) {
$this->fail('查询失败: ' . $e->getMessage());
}
$this->success([
'records' => $records,
'fields' => $fieldConfigs,
'total' => (int)$total,
'page' => $page,
'limit' => $limit,
'total_pages' => ceil($total / $limit),
]);
}
// ==================== 15. 今日统计 ====================
/**
* GET /MiniApp/dashboard
* Headers: Authorization: Bearer <token>
* 响应: { today: { inbound, pcb_test, battery, assembly, finished_test, warehouse, delivery }, total }
*/
public function dashboard()
{
$user = $this->tokenAuth();
try {
$pdo = \core\db\Db::pdo();
$tables = [
'inbound' => 'station_inbound',
'pcb_test' => 'pcb_test',
'battery' => 'station_battery',
'assembly' => 'product_assembly',
'finished_test' => 'station_finished_test',
'warehouse' => 'station_warehouse',
'delivery' => 'station_delivery',
];
$today = [];
foreach ($tables as $key => $table) {
$stmt = $pdo->prepare(
"SELECT COUNT(*) as cnt FROM DGZXY_{$table} WHERE operator = :op AND DATE(created_at) = CURDATE()"
);
$stmt->execute([':op' => $user['emp_name']]);
$today[$key] = (int)($stmt->fetch(\PDO::FETCH_ASSOC)['cnt'] ?? 0);
}
$total = array_sum($today);
} catch (\Throwable $e) {
$today = [];
$total = 0;
}
$this->success([
'today' => $today,
'total' => $total,
]);
}
// ==================== 16. 产品追溯查询 ====================
/**
* GET /MiniApp/productSearch?keyword=序列号
* Headers: Authorization: Bearer <token>
* 跨工位搜索序列号,返回产品全链路信息
*/
public function productSearch()
{
$user = $this->tokenAuth();
$keyword = trim($_GET['keyword'] ?? '');
if (empty($keyword)) {
$this->fail('请输入搜索关键词');
}
$pdo = \core\db\Db::pdo();
$result = [
'keyword' => $keyword,
'traces' => [],
];
// 每个表查询独立 try-catch,某个表不存在不影响其他查询
$queries = [
['source' => 'inbound', 'sql' =>
"SELECT 'inbound' as source, id, serial_no, product_type, product_model, test_status, operator, created_at
FROM DGZXY_station_inbound WHERE serial_no = :kw LIMIT 1", 'params' => [':kw' => $keyword]],
['source' => 'pcb_test', 'sql' =>
"SELECT 'pcb_test' as source, id, serial_no, product_type, product_model, static_power, input_output, test_status, operator, created_at
FROM DGZXY_pcb_test WHERE serial_no = :kw LIMIT 1", 'params' => [':kw' => $keyword]],
['source' => 'battery', 'sql' =>
"SELECT 'battery' as source, id, serial_no, voltage_platform, no_load_voltage, internal_resistance, operator, created_at
FROM DGZXY_station_battery WHERE serial_no = :kw LIMIT 1", 'params' => [':kw' => $keyword]],
['source' => 'assembly', 'sql' =>
"SELECT 'assembly' as source, id, finished_serial, battery_serial, pcb_serial, product_model, operator, created_at
FROM DGZXY_product_assembly
WHERE finished_serial = :kw OR battery_serial = :kw2 OR pcb_serial = :kw3
LIMIT 5", 'params' => [':kw' => $keyword, ':kw2' => $keyword, ':kw3' => $keyword]],
['source' => 'finished_test', 'sql' =>
"SELECT 'finished_test' as source, id, finished_serial, test_item, test_result, test_steps, product_model, operator, created_at
FROM DGZXY_station_finished_test WHERE finished_serial = :kw LIMIT 5", 'params' => [':kw' => $keyword]],
['source' => 'warehouse', 'sql' =>
"SELECT 'warehouse' as source, id, finished_serial, product_model, operator, created_at as in_time
FROM DGZXY_station_warehouse WHERE finished_serial = :kw LIMIT 1", 'params' => [':kw' => $keyword]],
['source' => 'delivery', 'sql' =>
"SELECT 'delivery' as source, id, finished_serial, customer_name, product_model, operator, created_at
FROM DGZXY_station_delivery WHERE finished_serial = :kw LIMIT 1", 'params' => [':kw' => $keyword]],
];
foreach ($queries as $q) {
try {
$stmt = $pdo->prepare($q['sql']);
$stmt->execute($q['params']);
while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
$result['traces'][] = $row;
}
} catch (\Throwable $e) {
// 表不存在时跳过,继续查询其他表
continue;
}
}
$result['count'] = count($result['traces']);
$this->success($result);
}
// ==================== 17. 修改密码 ====================
/**
* POST /MiniApp/changePassword
* Headers: Authorization: Bearer <token>
* 请求: { old_password, new_password }
*/
public function changePassword()
{
$user = $this->tokenAuth();
$data = $this->getJsonInput();
$oldPassword = $data['old_password'] ?? '';
$newPassword = $data['new_password'] ?? '';
if (empty($oldPassword)) $this->fail('请输入旧密码');
if (empty($newPassword)) $this->fail('请输入新密码');
if (strlen($newPassword) < 8) $this->fail('新密码长度不能少于8位');
// 验证旧密码
$employee = new Employee();
$valid = $employee->validateLogin($user['emp_no'], $oldPassword);
if (!$valid) {
$this->fail('旧密码错误');
}
// 更新密码
$employee->resetPassword($user['id'], $newPassword);
// 使所有 token 失效(强制重新登录)
try {
$pdo = \core\db\Db::pdo();
$pdo->prepare("UPDATE DGZXY_miniapp_token SET status = 0 WHERE employee_id = :eid")
->execute([':eid' => $user['id']]);
} catch (\Throwable $e) {}
$this->success([], '密码修改成功,请重新登录');
}
}
// ==================== 辅助函数 ====================
/**
* 解析工位字段配置(JSON字符串转数组)
*/
function parseFieldsConfig($stationType, $fieldsConfigJson)
{
$config = is_string($fieldsConfigJson) ? json_decode($fieldsConfigJson, true) : $fieldsConfigJson;
if (!is_array($config)) {
return getDefaultFieldsConfig($stationType);
}
return $config;
}
/**
* 获取工位默认字段配置
* 根据工位类型返回对应字段定义
*/
function getDefaultFieldsConfig($stationType)
{
$configs = [
'inbound' => [
['field' => 'product_type', 'label' => '产品类型', 'type' => 'select', 'required' => true, 'source' => 'types'],
['field' => 'product_model', 'label' => '产品型号', 'type' => 'select', 'required' => true, 'source' => 'models'],
['field' => 'serial_no', 'label' => '产品序列号', 'type' => 'text', 'required' => true],
],
'pcb_test' => [
['field' => 'product_type', 'label' => '产品类型', 'type' => 'select', 'required' => false, 'source' => 'types'],
['field' => 'product_model', 'label' => '产品型号', 'type' => 'select', 'required' => false, 'source' => 'models'],
['field' => 'serial_no', 'label' => 'PCB序列号', 'type' => 'text', 'required' => true],
['field' => 'static_power', 'label' => '静态功耗', 'type' => 'number', 'required' => false],
['field' => 'input_output', 'label' => '输入输出', 'type' => 'text', 'required' => false],
['field' => 'test_status', 'label' => '测试状态', 'type' => 'select', 'required' => false, 'options' => ['pass', 'fail', 'pending']],
],
'battery' => [
['field' => 'serial_no', 'label' => '产品序列号', 'type' => 'text', 'required' => true],
['field' => 'voltage_platform', 'label' => '电压平台(V)', 'type' => 'number', 'required' => false],
['field' => 'no_load_voltage', 'label' => '空载电压(V)', 'type' => 'number', 'required' => false],
['field' => 'internal_resistance', 'label' => '内阻(mOhm)', 'type' => 'number', 'required' => false],
],
'assembly' => [
['field' => 'finished_serial', 'label' => '成品序列号', 'type' => 'text', 'required' => true],
['field' => 'battery_serial', 'label' => '电池序列号', 'type' => 'text', 'required' => true],
['field' => 'pcb_serial', 'label' => 'PCB序列号', 'type' => 'text', 'required' => true],
['field' => 'product_model', 'label' => '产品型号', 'type' => 'select', 'required' => true, 'source' => 'models'],
],
'finished_test' => [
['field' => 'finished_serial', 'label' => '成品序列号', 'type' => 'text', 'required' => true],
['field' => 'test_item', 'label' => '测试项目', 'type' => 'text', 'required' => true],
['field' => 'test_result', 'label' => '测试结果', 'type' => 'select', 'required' => true, 'options' => ['pass', 'fail']],
['field' => 'test_steps', 'label' => '测试步骤', 'type' => 'textarea', 'required' => false],
['field' => 'product_model', 'label' => '产品型号', 'type' => 'select', 'required' => false, 'source' => 'models'],
],
'warehouse' => [
['field' => 'finished_serial', 'label' => '成品序列号', 'type' => 'text', 'required' => true],
['field' => 'product_model', 'label' => '产品型号', 'type' => 'select', 'required' => false, 'source' => 'models'],
],
'delivery' => [
['field' => 'finished_serial', 'label' => '成品序列号', 'type' => 'text', 'required' => true],
['field' => 'customer_name', 'label' => '客户名称', 'type' => 'select', 'required' => true, 'source' => 'customers'],
['field' => 'product_model', 'label' => '产品型号', 'type' => 'select', 'required' => false, 'source' => 'models'],
],
];
return $configs[$stationType] ?? [];
}