first commit

This commit is contained in:
wushumin
2026-04-16 11:17:18 +08:00
commit 5b9c398e68
98 changed files with 8701 additions and 0 deletions

View File

@@ -0,0 +1,49 @@
<?php
namespace app\api\controller;
use support\Request;
use app\common\model\User;
use app\common\service\AuthService;
class AuthController
{
public function login(Request $request)
{
$mobile = trim((string)$request->post('mobile', ''));
$code = trim((string)$request->post('code', ''));
if ($mobile === '' || $code === '') {
return jsonResponse(null, '参数错误', 400);
}
if (!preg_match('/^\d{11}$/', $mobile)) {
return jsonResponse(null, '手机号格式错误', 400);
}
$user = User::firstOrCreate(
['mobile' => $mobile],
['nickname' => '用户' . substr($mobile, -4), 'status' => 1]
);
if (intval($user->status) !== 1) {
return jsonResponse(null, '账号已禁用', 403);
}
$token = AuthService::issueUserToken($user);
return jsonResponse([
'token' => $token,
'user' => $user
], '登录成功');
}
public function me(Request $request)
{
return jsonResponse([
'user' => $request->user
]);
}
public function logout(Request $request)
{
AuthService::revokeUserToken($request->token ?? null);
return jsonResponse(null, '已退出登录');
}
}

View File

@@ -0,0 +1,132 @@
<?php
namespace app\api\controller;
use support\Request;
use app\common\service\OrderFlowService;
use app\common\service\PaymentService;
use app\common\model\Order;
class OrderController
{
public function create(Request $request)
{
$params = $request->post();
$userId = $request->user->id;
try {
$order = OrderFlowService::createOrder($params, $userId);
return jsonResponse([
'order_id' => $order->id,
'order_no' => $order->order_no,
'pay_amount' => $order->total_price
], '下单成功');
} catch (\Exception $e) {
return jsonResponse(null, '下单失败: ' . $e->getMessage(), 400);
}
}
public function list(Request $request)
{
$status = $request->get('status', 'all');
$userId = $request->user->id;
$query = Order::where('user_id', $userId);
if ($status !== 'all') {
$query->where('status', $status);
}
$orders = $query->orderBy('id', 'desc')->get();
return jsonResponse(['items' => $orders, 'total' => $orders->count()]);
}
public function detail(Request $request, $id)
{
$userId = $request->user->id;
$order = Order::with(['logs'])->where('id', $id)->where('user_id', $userId)->first();
if (!$order) {
return jsonResponse(null, '订单不存在', 404);
}
$timeline = [];
$isFirst = true;
foreach ($order->logs as $log) {
$timeline[] = [
'title' => $log->title,
'time' => $log->created_at->format('Y-m-d H:i:s'),
'desc' => $log->description,
'is_current' => $isFirst,
'is_done' => true
];
$isFirst = false;
}
return jsonResponse([
'id' => $order->id,
'order_no' => $order->order_no,
'category' => $order->category,
'service_type' => $order->service_type,
'status' => $order->status,
'is_fast' => (bool)$order->is_fast,
'express_company' => $order->express_company,
'express_no' => $order->express_no,
'timeline' => $timeline
]);
}
public function pay(Request $request)
{
$orderId = (int)$request->post('order_id');
$userId = $request->user->id;
$payType = trim((string)$request->post('pay_type', 'jsapi'));
$appId = trim((string)$request->post('app_id', ''));
$order = Order::where('id', $orderId)->where('user_id', $userId)->first();
if (!$order) {
return jsonResponse(null, '订单不存在', 404);
}
try {
if ($payType === 'native') {
$pay = PaymentService::createWechatNativePay($order);
return jsonResponse($pay, '支付发起成功');
}
if ($appId === '') {
return jsonResponse(null, '缺少 app_id无法发起 JSAPI 支付', 400);
}
$openid = trim((string)$request->post('openid', ''));
$pay = PaymentService::createWechatJsapiPay($order, $appId, $openid);
return jsonResponse($pay, '支付发起成功');
} catch (\Throwable $e) {
return jsonResponse(null, $e->getMessage(), 400);
}
}
public function ship(Request $request)
{
$orderId = (int)$request->post('order_id');
$expressCompany = trim($request->post('express_company', ''));
$expressNo = trim($request->post('express_no', ''));
if (!$expressCompany || !$expressNo) {
return jsonResponse(null, '物流信息不完整', 400);
}
$userId = $request->user->id;
$order = Order::where('id', $orderId)->where('user_id', $userId)->first();
if (!$order) {
return jsonResponse(null, '订单不存在', 404);
}
try {
OrderFlowService::userShip($order, $expressCompany, $expressNo);
return jsonResponse(null, '发货信息已提交');
} catch (\Exception $e) {
return jsonResponse(null, $e->getMessage(), 400);
}
}
}

View File

@@ -0,0 +1,103 @@
<?php
namespace app\api\controller;
use support\Request;
use app\common\model\Order;
use app\common\model\PaymentTransaction;
use app\common\model\WechatMerchant;
use app\common\service\OrderFlowService;
use app\common\service\WechatPayV3Client;
use Illuminate\Database\Capsule\Manager as DB;
class PayController
{
public function wechatNotify(Request $request)
{
$body = (string)$request->rawBody();
$timestamp = (string)$request->header('Wechatpay-Timestamp', '');
$nonce = (string)$request->header('Wechatpay-Nonce', '');
$signature = (string)$request->header('Wechatpay-Signature', '');
if ($timestamp === '' || $nonce === '' || $signature === '') {
return json(['code' => 'FAIL', 'message' => 'missing headers'], 400);
}
$merchants = WechatMerchant::where('status', 1)->get();
if ($merchants->count() === 0) {
return json(['code' => 'FAIL', 'message' => 'no merchant'], 500);
}
$client = new WechatPayV3Client($merchants->first());
$ok = $client->verifyPlatformSignature($timestamp, $nonce, $body, $signature);
if (!$ok) {
return json(['code' => 'FAIL', 'message' => 'invalid signature'], 400);
}
$payload = json_decode($body, true) ?: [];
$resource = $payload['resource'] ?? null;
if (!is_array($resource)) {
return json(['code' => 'FAIL', 'message' => 'invalid body'], 400);
}
$decrypt = null;
$matchedMerchant = null;
foreach ($merchants as $m) {
$apiV3Key = (string)($m->api_v3_key ?? '');
if ($apiV3Key === '') continue;
try {
$decrypt = $client->decryptNotifyResource($resource, $apiV3Key);
$matchedMerchant = $m;
break;
} catch (\Throwable $e) {
}
}
if (!$decrypt || !$matchedMerchant) {
return json(['code' => 'FAIL', 'message' => 'decrypt failed'], 400);
}
$outTradeNo = (string)($decrypt['out_trade_no'] ?? '');
$tradeState = (string)($decrypt['trade_state'] ?? '');
if ($outTradeNo === '') {
return json(['code' => 'FAIL', 'message' => 'missing out_trade_no'], 400);
}
$tx = PaymentTransaction::where('out_trade_no', $outTradeNo)->first();
if (!$tx) {
return json(['code' => 'SUCCESS', 'message' => 'OK']);
}
if ($tradeState !== 'SUCCESS') {
return json(['code' => 'SUCCESS', 'message' => 'OK']);
}
DB::beginTransaction();
try {
$tx->status = 'paid';
$tx->paid_at = date('Y-m-d H:i:s');
$tx->raw_json = $decrypt;
$tx->save();
$order = Order::find($tx->order_id);
if ($order) {
$order->pay_channel = 'wechat';
$order->pay_status = 'paid';
$order->pay_merchant_id = (int)$matchedMerchant->id;
$order->pay_out_trade_no = $outTradeNo;
if (!$order->pay_time) {
$order->pay_time = date('Y-m-d H:i:s');
}
$order->save();
OrderFlowService::payOrder($order);
}
DB::commit();
} catch (\Throwable $e) {
DB::rollBack();
return json(['code' => 'FAIL', 'message' => 'server error'], 500);
}
return json(['code' => 'SUCCESS', 'message' => 'OK']);
}
}

View File

@@ -0,0 +1,70 @@
<?php
namespace app\api\controller;
use support\Request;
use app\common\model\Report;
use app\common\model\Order;
class ReportController
{
// 获取C端自己的报告
public function detail(Request $request)
{
$orderId = (int)$request->get('order_id');
$userId = $request->user->id;
$order = Order::where('id', $orderId)->where('user_id', $userId)->first();
if (!$order) {
return jsonResponse(null, '订单不存在', 404);
}
$report = Report::with(['inspector'])->where('order_id', $orderId)->first();
if (!$report) {
return jsonResponse(null, '报告尚未出具', 404);
}
return jsonResponse([
'report_no' => $report->report_no,
'conclusion' => $report->conclusion,
'level' => $report->level,
'flaws' => $report->flaws_json,
'images' => $report->images_json,
'verify_code' => $report->verify_code,
'created_at' => $report->created_at->format('Y-m-d H:i:s'),
'inspector' => [
'name' => $report->inspector->nickname ?? $report->inspector->username,
]
]);
}
// 公开验证防伪码 (无需登录)
public function verify(Request $request)
{
$code = trim($request->get('code', ''));
if (!$code) {
return jsonResponse(null, '防伪码不能为空', 400);
}
$report = Report::with(['order', 'inspector'])->where('verify_code', $code)->first();
if (!$report) {
return jsonResponse(null, '无效的防伪码或报告不存在', 404);
}
return jsonResponse([
'report_no' => $report->report_no,
'conclusion' => $report->conclusion,
'level' => $report->level,
'flaws' => $report->flaws_json,
'images' => $report->images_json,
'created_at' => $report->created_at->format('Y-m-d H:i:s'),
'order' => [
'category' => $report->order->category,
'brand' => $report->order->brand,
'model' => $report->order->model,
],
'inspector' => [
'name' => $report->inspector->nickname ?? $report->inspector->username,
]
], '验证成功,该报告真实有效');
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace app\api\controller;
use support\Request;
use Webman\Http\UploadFile;
class UploadController
{
public function image(Request $request)
{
$file = $request->file('file');
if (!$file || !$file->isValid()) {
return jsonResponse(null, '未找到文件或文件无效', 400);
}
$ext = strtolower($file->getUploadExtension());
if (!in_array($ext, ['jpg', 'jpeg', 'png', 'gif', 'webp'])) {
return jsonResponse(null, '仅支持图片文件', 400);
}
$dir = public_path() . '/upload/images/' . date('Ymd');
if (!is_dir($dir)) {
mkdir($dir, 0777, true);
}
$filename = uniqid() . bin2hex(random_bytes(4)) . '.' . $ext;
$path = $dir . '/' . $filename;
$file->move($path);
$url = '/upload/images/' . date('Ymd') . '/' . $filename;
return jsonResponse([
'url' => $url,
'name' => $file->getUploadName(),
], '上传成功');
}
}

View File

@@ -0,0 +1,49 @@
<?php
namespace app\api\controller;
use support\Request;
use app\common\model\Order;
use app\common\model\Report;
use app\common\model\User;
class UserController
{
public function stat(Request $request)
{
$userId = $request->user->id;
$totalOrders = Order::where('user_id', $userId)->count();
$totalReports = Report::whereHas('order', function ($query) use ($userId) {
$query->where('user_id', $userId);
})->count();
return jsonResponse([
'total_orders' => $totalOrders,
'total_reports' => $totalReports
]);
}
public function updateInfo(Request $request)
{
$userId = $request->user->id;
$user = User::find($userId);
if (!$user) {
return jsonResponse(null, '用户异常', 404);
}
$nickname = trim($request->post('nickname', ''));
$avatar = trim($request->post('avatar', ''));
if ($nickname) {
$user->nickname = $nickname;
}
if ($avatar) {
$user->avatar = $avatar;
}
$user->save();
return jsonResponse($user, '更新成功');
}
}

View File

@@ -0,0 +1,205 @@
<?php
namespace app\api\controller;
use support\Request;
use app\common\model\User;
use app\common\model\WechatApp;
use app\common\model\UserWechatIdentity;
use app\common\service\AuthService;
class WechatAuthController
{
public function appList(Request $request)
{
$type = trim((string)$request->get('type', ''));
$query = WechatApp::query()->where('status', 1);
if ($type !== '') {
$query->where('type', $type);
}
$list = $query->select(['id', 'name', 'type', 'app_id'])->orderByDesc('id')->get();
return jsonResponse($list);
}
public function miniLogin(Request $request)
{
$appId = trim((string)$request->post('app_id', ''));
$code = trim((string)$request->post('code', ''));
if ($appId === '' || $code === '') {
return jsonResponse(null, '参数错误', 400);
}
try {
$app = $this->getApp($appId, 'mini');
} catch (\Throwable $e) {
return jsonResponse(null, $e->getMessage(), 400);
}
$secret = (string)($app->app_secret ?? '');
if ($secret === '') {
return jsonResponse(null, '未配置 app_secret', 400);
}
$url = 'https://api.weixin.qq.com/sns/jscode2session?appid=' . urlencode($appId) .
'&secret=' . urlencode($secret) .
'&js_code=' . urlencode($code) .
'&grant_type=authorization_code';
$res = $this->httpGetJson($url);
$openid = isset($res['openid']) ? trim((string)$res['openid']) : '';
$unionid = isset($res['unionid']) ? trim((string)$res['unionid']) : '';
if ($openid === '') {
$msg = $res['errmsg'] ?? '获取 openid 失败';
return jsonResponse(null, (string)$msg, 400);
}
$user = $this->resolveUserByWechatIdentity($appId, $openid, $unionid, 'mini');
if (intval($user->status) !== 1) {
return jsonResponse(null, '账号已禁用', 403);
}
$this->upsertIdentity($user->id, $appId, $openid, $unionid, 'mini');
$user->openid = $openid;
$user->save();
$token = AuthService::issueUserToken($user);
return jsonResponse([
'token' => $token,
'user' => $user,
'openid' => $openid,
'app_id' => $appId,
], '登录成功');
}
public function h5Login(Request $request)
{
$appId = trim((string)$request->post('app_id', ''));
$code = trim((string)$request->post('code', ''));
if ($appId === '' || $code === '') {
return jsonResponse(null, '参数错误', 400);
}
try {
$app = $this->getApp($appId, 'h5');
} catch (\Throwable $e) {
return jsonResponse(null, $e->getMessage(), 400);
}
$secret = (string)($app->app_secret ?? '');
if ($secret === '') {
return jsonResponse(null, '未配置 app_secret', 400);
}
$url = 'https://api.weixin.qq.com/sns/oauth2/access_token?appid=' . urlencode($appId) .
'&secret=' . urlencode($secret) .
'&code=' . urlencode($code) .
'&grant_type=authorization_code';
$res = $this->httpGetJson($url);
$openid = isset($res['openid']) ? trim((string)$res['openid']) : '';
$unionid = isset($res['unionid']) ? trim((string)$res['unionid']) : '';
if ($openid === '') {
$msg = $res['errmsg'] ?? '获取 openid 失败';
return jsonResponse(null, (string)$msg, 400);
}
$user = $this->resolveUserByWechatIdentity($appId, $openid, $unionid, 'h5');
if (intval($user->status) !== 1) {
return jsonResponse(null, '账号已禁用', 403);
}
$this->upsertIdentity($user->id, $appId, $openid, $unionid, 'h5');
$user->openid = $openid;
$user->save();
$token = AuthService::issueUserToken($user);
return jsonResponse([
'token' => $token,
'user' => $user,
'openid' => $openid,
'app_id' => $appId,
], '登录成功');
}
public function h5AuthorizeUrl(Request $request)
{
$appId = trim((string)$request->get('app_id', ''));
$redirectUri = trim((string)$request->get('redirect_uri', ''));
$scope = trim((string)$request->get('scope', 'snsapi_base'));
$state = trim((string)$request->get('state', ''));
if ($appId === '' || $redirectUri === '') {
return jsonResponse(null, '参数错误', 400);
}
if (!in_array($scope, ['snsapi_base', 'snsapi_userinfo'], true)) {
return jsonResponse(null, 'scope 不合法', 400);
}
try {
$this->getApp($appId, 'h5');
} catch (\Throwable $e) {
return jsonResponse(null, $e->getMessage(), 400);
}
$url = 'https://open.weixin.qq.com/connect/oauth2/authorize?appid=' . urlencode($appId) .
'&redirect_uri=' . urlencode($redirectUri) .
'&response_type=code&scope=' . urlencode($scope) .
'&state=' . urlencode($state) .
'#wechat_redirect';
return jsonResponse(['url' => $url]);
}
private function getApp(string $appId, string $type): WechatApp
{
$row = WechatApp::where('app_id', $appId)->where('status', 1)->first();
if (!$row) {
throw new \RuntimeException('AppID 未配置或已停用');
}
if ((string)$row->type !== $type) {
throw new \RuntimeException('AppID 类型不匹配');
}
return $row;
}
private function httpGetJson(string $url): array
{
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
$body = curl_exec($ch);
if ($body === false) {
$err = curl_error($ch);
curl_close($ch);
throw new \RuntimeException('微信接口请求失败: ' . $err);
}
curl_close($ch);
return json_decode($body, true) ?: [];
}
private function resolveUserByWechatIdentity(string $appId, string $openid, string $unionid, string $scene): User
{
if ($unionid !== '') {
$identity = UserWechatIdentity::where('unionid', $unionid)->first();
if ($identity) {
$user = User::find($identity->user_id);
if ($user) return $user;
}
}
$identity = UserWechatIdentity::where('app_id', $appId)->where('openid', $openid)->first();
if ($identity) {
$user = User::find($identity->user_id);
if ($user) return $user;
}
return User::create([
'openid' => $openid,
'nickname' => $scene === 'mini' ? '小程序用户' : '微信用户',
'status' => 1,
]);
}
private function upsertIdentity(int $userId, string $appId, string $openid, string $unionid, string $scene): void
{
UserWechatIdentity::updateOrCreate(
['user_id' => $userId, 'app_id' => $appId],
['openid' => $openid, 'unionid' => $unionid ?: null, 'scene' => $scene]
);
}
}