diff --git a/app/Amqp/Consumer/DevicOrderConsumer.php b/app/Amqp/Consumer/DevicOrderConsumer.php
new file mode 100644
index 0000000..08a2d1c
--- /dev/null
+++ b/app/Amqp/Consumer/DevicOrderConsumer.php
@@ -0,0 +1,73 @@
+getBody();
+ $order = Order::query()
+ ->select(['id', 'store_id', 'money'])
+ ->where(['order_main_id' => $orderMainId, 'type' => 4, 'dm_state' => 2])
+ ->first();
+
+ if (is_null($order)||!$order) {
+ return Result::ACK;
+ }
+
+ $deviceNames = SpeakerDevic::query()
+ ->select(['device_name'])
+ ->where(['store_id' => $order['store_id'], 'is_bind' => SpeakerDevic::IS_BIND_YES])
+ ->get()
+ ->toArray();
+
+ if (empty($deviceNames)||!$deviceNames) {
+ return Result::ACK;
+ }
+
+ $msg = "{\"msg\":\"到账".$order['money']."元\"}";
+ $res = $this->deviceService->pubMsgToStoreByDevName($deviceNames, $msg);
+
+ if ($res == true) {
+ return Result::ACK;
+ } else {
+ return Result::REQUEUE;
+ }
+
+ } catch (\Exception $e) {
+ return Result::REQUEUE;
+ }
+ }
+
+ public function isEnable(): bool
+ {
+ if(env('APP_ENV') == 'local') {
+ return false;
+ }
+ return parent::isEnable();
+ }
+}
diff --git a/app/Commons/Log.php b/app/Commons/Log.php
index bf5fb24..29a4e84 100644
--- a/app/Commons/Log.php
+++ b/app/Commons/Log.php
@@ -61,7 +61,7 @@ class Log
]
];
$client->post(
- 'http://39.96.12.39:3100/loki/api/v1/push',
+ env('LOG_HOST','http://39.96.12.39:3100').'/loki/api/v1/push',
[
'headers'=>[
'Content-Type'=>'application/json'
diff --git a/app/Constants/ErrorCode.php b/app/Constants/ErrorCode.php
index b361172..ca97510 100644
--- a/app/Constants/ErrorCode.php
+++ b/app/Constants/ErrorCode.php
@@ -38,4 +38,20 @@ class ErrorCode extends AbstractConstants
* @Message("Ssdb Error!")
*/
const SSDB_ERROR = 600;
+
+ /**
+ * @Message("Upload failure!")
+ */
+ const UPLOAD_INVALID = 200;
+
+ /**
+ * @Message("Order failure!")
+ */
+ const ORDER_FAILURE = 300;
+
+ /**
+ * @Message("Pay failure!")
+ */
+ const PAY_FAILURE = 400;
+
}
diff --git a/app/Constants/LogLabel.php b/app/Constants/LogLabel.php
index c09c8df..c78d945 100644
--- a/app/Constants/LogLabel.php
+++ b/app/Constants/LogLabel.php
@@ -18,4 +18,14 @@ class LogLabel extends AbstractConstants
const SSDB_LOG = 'ssdb_log';
const COUPON_LOG = 'coupon_log';
+
+ /**
+ * @Message("Device Speaker Log Label")
+ */
+ const DEVICE_LOG = 'device_log';
+
+ /**
+ * @Message("Pay Notice Log Label")
+ */
+ const PAY_NOTIFY_WXMINI = 'notify_wxmini';
}
diff --git a/app/Controller/AbstractController.php b/app/Controller/AbstractController.php
index b7429ea..30df791 100644
--- a/app/Controller/AbstractController.php
+++ b/app/Controller/AbstractController.php
@@ -15,6 +15,7 @@ use Hyperf\Di\Annotation\Inject;
use Hyperf\HttpServer\Contract\RequestInterface;
use Hyperf\HttpServer\Contract\ResponseInterface;
use Psr\Container\ContainerInterface;
+use Hyperf\Validation\Contract\ValidatorFactoryInterface;
use App\Commons\Log;
abstract class AbstractController
@@ -43,4 +44,11 @@ abstract class AbstractController
* @var ResponseInterface
*/
protected $response;
+
+ /**
+ * @Inject
+ * @var ValidatorFactoryInterface
+ */
+ protected $validationFactory;
+
}
diff --git a/app/Controller/AttachmentController.php b/app/Controller/AttachmentController.php
new file mode 100644
index 0000000..efbf45d
--- /dev/null
+++ b/app/Controller/AttachmentController.php
@@ -0,0 +1,68 @@
+request->file('upload');
+ $type = $this->request->input('type', '');
+
+ $fileName = $this->attachmentService->formUpload($file, $type, $filesystem, 'file');
+
+ return $this->success(['file_path' => $fileName]);
+ }
+
+ /**
+ * 单图表单上传
+ * @param ImageRequest $request
+ * @param Filesystem $filesystem
+ * @return \Psr\Http\Message\ResponseInterface
+ */
+ public function uploadImage(ImageRequest $request, Filesystem $filesystem)
+ {
+ $file = $this->request->file('upload');
+ $type = $this->request->input('type', '');
+
+ $fileName = $this->attachmentService->formUpload($file, $type, $filesystem);
+
+ return $this->success(['file_path' => $fileName]);
+ }
+
+ /**
+ * 单图base64上传
+ * @param ImageBase64Request $request
+ * @param Filesystem $filesystem
+ * @return \Psr\Http\Message\ResponseInterface
+ */
+ public function uploadImageByBase64(ImageBase64Request $request, Filesystem $filesystem)
+ {
+ $base64Code = $this->request->input('upload');
+ $type = $this->request->input('type', '');
+
+ $fileName = $this->attachmentService->base64Upload($base64Code, $type, $filesystem);
+
+ return $this->success(['file_path' => $fileName]);
+ }
+
+}
\ No newline at end of file
diff --git a/app/Controller/BaseController.php b/app/Controller/BaseController.php
index 40d2813..ffa8c77 100644
--- a/app/Controller/BaseController.php
+++ b/app/Controller/BaseController.php
@@ -13,9 +13,18 @@ namespace App\Controller;
use Hyperf\HttpServer\Contract\ResponseInterface;
use Psr\Http\Message\ResponseInterface as Psr7ResponseInterface;
+use Hyperf\HttpMessage\Stream\SwooleStream;
class BaseController extends AbstractController
{
+
+ public function w7result($statusCode,$data = ''){
+ return $this->response
+ ->withHeader('Content-Type', 'application/text')
+ ->withStatus($statusCode)
+ ->withBody(new SwooleStream($data));
+ }
+
public function result($code, $data, $message = '成功'):Psr7ResponseInterface
{
$status = 'ok';
diff --git a/app/Controller/DeviceController.php b/app/Controller/DeviceController.php
new file mode 100644
index 0000000..d7ec213
--- /dev/null
+++ b/app/Controller/DeviceController.php
@@ -0,0 +1,114 @@
+validationFactory->make(
+ $this->request->all(),
+ [
+ 'store_id' => 'required|nonempty|integer',
+ 'device_name' => 'required|nonempty|alpha_num',
+ ],
+ [
+ 'store_id.required' => '参数不正确',
+ 'store_id.nonempty' => '参数不正确',
+ 'store_id.integer' => '参数不正确',
+ 'device_name.required' => '参数不正确',
+ 'device_name.nonempty' => '参数不正确',
+ 'device_name.alpha_num' => '参数不正确',
+ ]
+ );
+
+ if ($validator->fails()) {
+ // Handle exception
+ throw new ValidationException($validator);
+ return;
+ }
+
+ $store_id = $this->request->input('store_id');
+ $device_name = $this->request->input('device_name');
+
+ $sd = $this->deviceService->bindByStoreId($device_name, $store_id);
+
+ if (is_null($sd)) {
+ return $this->result(100, '', '绑定失败: 设备号已经被绑定或不存在');
+ }
+
+ return $this->success($sd, '绑定成功');
+
+ }
+
+ public function list()
+ {
+ $validator = $this->validationFactory->make(
+ $this->request->all(),
+ [
+ 'store_id' => 'required|nonempty|integer',
+ ],
+ [
+ 'store_id.required' => '参数不正确',
+ 'store_id.nonempty' => '参数不正确',
+ 'store_id.integer' => '参数不正确',
+ ]
+ );
+
+ if ($validator->fails()) {
+ // Handle exception
+ throw new ValidationException($validator);
+ return;
+ }
+
+ $store_id = $this->request->input('store_id');
+
+ $devices = $this->deviceService->getListByStoreId($store_id);
+
+ return $this->success($devices);
+ }
+
+ public function unbind()
+ {
+ $validator = $this->validationFactory->make(
+ $this->request->all(),
+ [
+ 'bind_id' => 'required|nonempty|integer',
+ ],
+ [
+ 'bind_id.required' => '参数不正确',
+ 'bind_id.nonempty' => '参数不正确',
+ 'bind_id.integer' => '参数不正确',
+ ]
+ );
+
+ if ($validator->fails()) {
+ // Handle exception
+ throw new ValidationException($validator);
+ return;
+ }
+
+ $bind_id = $this->request->input('bind_id');
+
+ $unbind_num = $this->deviceService->unbindById($bind_id);
+
+ if ($unbind_num == 0) {
+ return $this->result(100, '', '解绑失败: 设备已经解绑或不存在');
+ }
+
+ return $this->success(['unbind' => $unbind_num]);
+ }
+}
\ No newline at end of file
diff --git a/app/Controller/IndexController.php b/app/Controller/IndexController.php
index 3864423..417e4d4 100644
--- a/app/Controller/IndexController.php
+++ b/app/Controller/IndexController.php
@@ -11,6 +11,13 @@ declare(strict_types=1);
*/
namespace App\Controller;
+use Hyperf\HttpServer\Annotation\AutoController;
+use League\Flysystem\Filesystem;
+
+/**
+ * @AutoController()
+ * @package App\Controller
+ */
class IndexController extends AbstractController
{
public function index()
@@ -20,7 +27,23 @@ class IndexController extends AbstractController
return [
'method' => $method,
- 'message' => "Hello22222 {$user}.",
+ 'message' => floatval(2.00),
];
}
+
+ public function example(Filesystem $filesystem)
+ {
+ $file = $this->request->file('upload');
+
+ var_dump($file);die;
+ $fileContent = file_get_contents($file->getRealPath());
+ var_dump($fileContent);die;
+
+
+
+
+ $stream = fopen($file->getRealPath(),'r+');
+ $filesystem->writeStream('uplaods/'.$file->getClientFilename(),$stream);
+ fclose($stream);
+ }
}
diff --git a/app/Controller/NotifyController.php b/app/Controller/NotifyController.php
new file mode 100644
index 0000000..9f47c82
--- /dev/null
+++ b/app/Controller/NotifyController.php
@@ -0,0 +1,380 @@
+request->getQueryParams();
+ $post = $this->request->getParsedBody();
+ $cookie = $this->request->getCookieParams();
+ $files = $this->request->getUploadedFiles();
+ $server = $this->request->getServerParams();
+ $xml = $this->request->getBody()->getContents();
+
+ $app['request'] = new Request($get,$post,[],$cookie,$files,$server,$xml);
+
+ // 通知回调,进行业务处理
+ $response = $app->handlePaidNotify(function ($message, $fail) use ($app) {
+
+ Db::beginTransaction();
+ try {
+ // 支付失败或者通知失败
+ if (
+ empty($message)
+ || $message['return_code'] != 'SUCCESS'
+ || !isset($message['result_code'])
+ || $message['result_code'] != 'SUCCESS'
+ ) {
+ $this->log->event(
+ LogLabel::PAY_NOTIFY_WXMINI,
+ $message
+ );
+ Db::rollBack();
+ $fail('Unknown error but FAIL');
+ }
+
+ // 查询订单
+ $orderMain = OrderMain::query()
+ ->where([
+ 'global_order_id' => $message['out_trade_no'],
+ 'type' => OrderMain::ORDER_TYPE_ONLINE
+ ])
+ ->first();
+
+ // 订单不存在
+ if (empty($orderMain)) {
+ $this->log->event(
+ LogLabel::PAY_NOTIFY_WXMINI,
+ ['global_order_id_fail' => $message['out_trade_no']]
+ );
+ Db::rollBack();
+ return true;
+ }
+
+ // 修改订单、子订单状态
+ $currentTime = time();
+ $orderMain->state = OrderMain::ORDER_STATE_UNTAKE;
+ $orderMain->time_pay = $currentTime;
+ $orderMain->pay_time = date('Y-m-d H:i:s', $currentTime);
+ $orderMain->save();
+
+ $upOrder = Order::query()
+ ->where(['order_main_id' => $orderMain->id])
+ ->update(['state' => OrderMain::ORDER_STATE_UNTAKE, 'pay_time' => $orderMain->pay_time]);
+
+ // 更新商户销量
+ $upStoreScore = Store::query()
+ ->whereIn('id', explode(',', $orderMain->store_ids))
+ ->update(['score' => Db::raw('score+1')]);
+
+ // 更新商品库存和销量
+ $orders = Order::query()->select(['id', 'money', 'user_id', 'store_id', 'pay_time'])
+ ->where(['order_main_id' => $orderMain->id])
+ ->get()
+ ->toArray();
+ $orderGoods = OrderGoods::query()->select(['good_id AS id', 'number', 'combination_id'])
+ ->whereIn('order_id', array_values(array_column($orders, 'id')))
+ ->get()
+ ->toArray();
+ foreach ($orderGoods as $key => &$goodsItem) {
+
+ $goods = Goods::find($goodsItem['id']);
+
+ // 库存处理,有规格
+ if ($goodsItem['combination_id']) {
+ $combination = SpecCombination::find($goodsItem['combination_id']);
+ $combination->number = $combination->number - $goodsItem['number'];
+ $combination->save();
+ } else {
+ $goods->inventory = $goods->inventory - $goodsItem['number'];
+ }
+
+ $goods->sales = $goods->sales - $goodsItem['number'];
+ $goods->save();
+
+ }
+
+ // 月销流水
+ $statistics = [];
+ foreach ($orders as $key => &$order) {
+ $statistics[] = [
+ 'money' => $order['money'],
+ 'user_id' => $order['user_id'],
+ 'store_id' => $order['store_id'],
+ 'market_id' => $orderMain->market_id,
+ 'order_id' => $order['id'],
+ 'createtime' => strtotime($order['pay_time']),
+ ];
+ }
+
+ if (is_array($statistics) && !empty($statistics)) {
+ $inSalesStatistics = OrderSalesStatistic::query()->insert($statistics);
+ }
+
+ // 喇叭通知,兼容旧音响,MQTT+IOT
+ $res = $this->mqttSpeakerService->speakToStore($orderMain->id);
+ $res = $this->deviceService->pubMsgToStoreByOrderMainId($orderMain->id);
+
+ // 公众号模板消息
+ $res = $this->miniprogramService->sendTemMsgForOnlineOrder($orderMain->id);
+
+ // 打印订单,自动打印 TODO 后续优化调用逻辑
+ $res = $this->feiePrintService->feiePrint($orderMain->global_order_id);
+
+ Db::commit();
+ return true;
+
+ } catch (Exception $e) {
+
+ $this->log->event(
+ LogLabel::PAY_NOTIFY_WXMINI,
+ ['exception_fail' => $e->getMessage()]
+ );
+ Db::rollBack();
+ $fail('Exception');
+ }
+
+ });
+
+ return $this->response
+ ->withHeader('Content-Type', 'text/xml')
+ ->withStatus(200)
+ ->withBody(new SwooleStream($response->getContent()));
+
+ }
+
+ public function wxminiOffline()
+ {
+
+ $config = config('wxpay');
+ $app = Factory::payment($config);
+ $app['guzzle_handler'] = CoroutineHandler::class;
+
+ $get = $this->request->getQueryParams();
+ $post = $this->request->getParsedBody();
+ $cookie = $this->request->getCookieParams();
+ $files = $this->request->getUploadedFiles();
+ $server = $this->request->getServerParams();
+ $xml = $this->request->getBody()->getContents();
+
+ $app['request'] = new Request($get,$post,[],$cookie,$files,$server,$xml);
+
+ // 通知回调,进行业务处理
+ $response = $app->handlePaidNotify(function ($message, $fail) use ($app) {
+
+ Db::beginTransaction();
+ try {
+ // 支付失败或者通知失败
+ if (
+ empty($message)
+ || $message['return_code'] != 'SUCCESS'
+ || !isset($message['result_code'])
+ || $message['result_code'] != 'SUCCESS'
+ ) {
+ $this->log->event(
+ LogLabel::PAY_NOTIFY_WXMINI,
+ $message
+ );
+ Db::rollBack();
+ $fail('Unknown error but FAIL');
+ }
+
+ // 查询订单
+ $orderMain = OrderMain::query()
+ ->where([
+ 'global_order_id' => $message['out_trade_no'],
+ 'type' => OrderMain::ORDER_TYPE_OFFLINE
+ ])
+ ->first();
+
+ // 订单不存在
+ if (empty($orderMain)) {
+ $this->log->event(
+ LogLabel::PAY_NOTIFY_WXMINI,
+ ['global_order_id_fail' => $message['out_trade_no']]
+ );
+ Db::rollBack();
+ return true;
+ }
+
+ // 修改订单、子订单状态
+ $currentTime = time();
+ $orderMain->state = OrderMain::ORDER_STATE_UNTAKE;
+ $orderMain->dm_state = OrderMain::ORDER_STATE_UNTAKE;
+ $orderMain->time_pay = $currentTime;
+ $orderMain->pay_time = date('Y-m-d H:i:s', $currentTime);
+ $orderMain->save();
+
+ $upOrder = Order::query()
+ ->where(['order_main_id' => $orderMain->id])
+ ->update([
+ 'state' => OrderMain::ORDER_STATE_UNTAKE,
+ 'dm_state' => OrderMain::ORDER_STATE_UNTAKE,
+ 'pay_time' => date('Y-m-d H:i:s', $currentTime)
+ ]);
+
+ // 查询子订单,当面付目前实际上只有一个子订单
+ $orders = Order::query()->select(['id', 'money', 'user_id', 'store_id', 'pay_time'])
+ ->where(['order_main_id' => $orderMain->id])
+ ->get()
+ ->toArray();
+
+ // 商户钱包、流水资金、奖励、发布模板消息处理
+ foreach ($orders as $key => $orderItem) {
+
+ $recordBase = [
+ 'user_id' => $orderItem['user_id'],
+ 'order_id' => $orderItem['id'],
+ 'store_id' => $orderItem['store_id'],
+ 'type' => 1,
+ 'time' => date('Y-m-d H:i:s', $currentTime),
+ 'add_time' => $currentTime,
+ ];
+
+ // 钱包
+ $store = Store::find($orderItem['store_id']);
+ $store->store_wallet = bcadd($store->store_wallet, $orderItem['money'], 2);
+ $store->save();
+
+ // 流水
+ $record = [
+ 'money' => $orderItem['money'],
+ 'note' => '当面付订单收入',
+ 'category' => 2,
+ ];
+ StoreAccount::query()->insert(array_merge($recordBase, $record));
+
+ // 平台新用户奖励给商户
+ $isStageNewUser = $this->userService->isStageNewUser($orderItem['user_id'], $orderMain->id);
+ $needAward = false;
+ $awardAmount = 0;
+ if ($isStageNewUser) {
+ $awardAmount = SystemConfig::query()->where(['type' => 1, 'menu_name' => 'award_new_user'])->value('value');
+ // 流水
+ $record = [
+ 'money' => $awardAmount,
+ 'note' => '新用户下单成功,平台奖励',
+ 'category' => 3,
+ ];
+ $needAward = true;
+ } else {
+ $isStoreFirstOrderToday = $this->userService->isStoreFirstOrderToday($orderItem['user_id'],$orderItem['store_id'],$orderItem['id'], self::AWARD_LIMIT_AMOUNT);
+ if ($isStoreFirstOrderToday && $orderItem['money'] >= self::AWARD_LIMIT_AMOUNT) {
+ $awardAmount = SystemConfig::query()->where(['type' => 1, 'menu_name' => 'award_each_order'])->value('value');
+ // 流水
+ $record = [
+ 'money' => $awardAmount,
+ 'note' => '用户下单成功,平台奖励',
+ 'category' => 4,
+ ];
+ $needAward = true;
+ }
+ }
+
+ if ($needAward && $awardAmount) {
+ // 奖励钱包
+ $store->refresh();
+ $store->award_money = bcadd($store->award_money, $awardAmount, 2);
+ $store->save();
+
+ // 流水
+ StoreAccount::query()->insert(array_merge($recordBase, $record));
+
+ // 发布公众号消息
+ $openid = Users::query()->where(['id' => $store['user_id']])->value('openid');
+ $res = $this->miniprogramService->sendTemMsgForAward($record['money'], $record['note'], $openid, $recordBase['time']);
+ }
+ }
+
+ // 喇叭通知,兼容旧音响,MQTT+IOT
+ $res = $this->mqttSpeakerService->speakToStore($orderMain->id);
+ $res = $this->deviceService->pubMsgToStoreByOrderMainId($orderMain->id);
+
+ // 公众号模板消息
+ $res = $this->miniprogramService->sendTemMsgForOfflineOrder($orderMain->id);
+
+ Db::commit();
+ return true;
+
+ } catch (Exception $e) {
+
+ $this->log->event(
+ LogLabel::PAY_NOTIFY_WXMINI,
+ ['exception_fail' => $e->getMessage()]
+ );
+ Db::rollBack();
+ $fail('Exception');
+ }
+
+ });
+
+ return $this->response
+ ->withHeader('Content-Type', 'text/xml')
+ ->withStatus(200)
+ ->withBody(new SwooleStream($response->getContent()));
+ }
+}
\ No newline at end of file
diff --git a/app/Controller/OrderController.php b/app/Controller/OrderController.php
new file mode 100644
index 0000000..f26045d
--- /dev/null
+++ b/app/Controller/OrderController.php
@@ -0,0 +1,38 @@
+orderService->addOnlineOrder($request->validated());
+ if (!is_int($orderMainId)) {
+ return $this->result(ErrorCode::ORDER_FAILURE, '', $orderMainId);
+ }
+ return $this->success(['order_id' => $orderMainId]);
+ }
+
+ public function addOfflineOrder(OrderOfflineRequest $request)
+ {
+ $orderMainId = $this->orderService->addOfflineOrder($request->validated());
+ if (!is_int($orderMainId)) {
+ return $this->result(ErrorCode::ORDER_FAILURE, '', $orderMainId);
+ }
+ return $this->success(['order_id' => $orderMainId]);
+ }
+}
\ No newline at end of file
diff --git a/app/Controller/PaymentController.php b/app/Controller/PaymentController.php
new file mode 100644
index 0000000..9fc92ae
--- /dev/null
+++ b/app/Controller/PaymentController.php
@@ -0,0 +1,136 @@
+validated();
+
+ $config = config('wxpay');
+ $app = Factory::payment($config);
+ $app['guzzle_handler'] = CoroutineHandler::class;
+
+ // 待支付的,未超时(15min,900sec)的订单
+ $orderMain = OrderMain::query()
+ ->where(['state' => OrderMain::ORDER_STATE_UNPAY, 'id' => $data['order_id']])
+ ->where('time', '>=', date('Y-m-d H:i:s', (time()-900)))
+ ->first();
+
+ if (empty($orderMain)) {
+ return $this->result(ErrorCode::PAY_FAILURE, $data,'订单不存在或已失效');
+ }
+
+ $result = $app->order->unify([
+ 'body' => '懒族生活 - 外卖下单',
+ 'out_trade_no' => $orderMain->global_order_id,
+ // 'total_fee' => bcmul(floatval($orderMain->money), 100, 0),
+ 'total_fee' => 1,
+ 'notify_url' => config('site_host') . '/wechat/notify/wxminionline',
+ 'trade_type' => 'JSAPI',
+ 'openid' => $data['openid'],
+ ]);
+
+ // 返回支付参数给前端
+ $parameters = [
+ 'appId' => $result['appid'],
+ 'timeStamp' => '' . time() . '',
+ 'nonceStr' => uniqid(),
+ 'package' => 'prepay_id=' . $result['prepay_id'],
+ 'signType' => 'MD5'
+ ];
+
+ $parameters['paySign'] = $this->signture($parameters, $config['key']);
+
+ return $this->success($parameters);
+ }
+
+ public function wxminiPayOffline(WxminiPayRequest $request){
+
+ $data = $request->validated();
+
+ $config = config('wxpay');
+ $app = Factory::payment($config);
+ $app['guzzle_handler'] = CoroutineHandler::class;
+
+ // 待支付的,未超时(15min,900sec)的订单
+ $orderMain = OrderMain::query()
+ ->where(['dm_state' => OrderMain::ORDER_STATE_UNPAY, 'id' => $data['order_id']])
+ ->first();
+
+ if (empty($orderMain)) {
+ return $this->result(ErrorCode::PAY_FAILURE, $data,'订单不存在或已失效');
+ }
+
+ $result = $app->order->unify([
+ 'body' => '懒族生活 - 当面支付',
+ 'out_trade_no' => $orderMain->global_order_id,
+ 'total_fee' => bcmul(floatval($orderMain->money), 100, 0),
+ 'notify_url' => config('site_host') . '/wechat/notify/wxminioffline',
+ 'trade_type' => 'JSAPI',
+ 'openid' => $data['openid'],
+ ]);
+
+ // 返回支付参数给前端
+ $parameters = [
+ 'appId' => $result['appid'],
+ 'timeStamp' => '' . time() . '',
+ 'nonceStr' => uniqid(),
+ 'package' => 'prepay_id=' . $result['prepay_id'],
+ 'signType' => 'MD5'
+ ];
+
+ $parameters['paySign'] = $this->signture($parameters, $config['key']);
+
+ return $this->success($parameters);
+ }
+
+ /**
+ * 支付参数加签
+ * @param $parameters
+ * @param $key
+ * @return string
+ */
+ private function signture($parameters, $key)
+ {
+ // 按字典序排序参数
+ ksort($parameters);
+
+ // http_query
+ $queryParams = $this->http_query($parameters);
+
+ // 加入KEY
+ $queryParams = $queryParams . "&key=" . $key;
+
+ // MD5加密
+ $queryParams = md5($queryParams);
+
+ // 字符转为大写
+ return strtoupper($queryParams);
+ }
+
+ /**
+ * 参数转为http query字串
+ * @param $parameters
+ * @return string
+ */
+ private function http_query($parameters) {
+
+ $http_query = [];
+ foreach ($parameters as $key => $value) {
+ $http_query[] = $key.'='.$value;
+ }
+
+ return implode('&', $http_query);
+ }
+
+}
\ No newline at end of file
diff --git a/app/Controller/StoreController.php b/app/Controller/StoreController.php
new file mode 100644
index 0000000..d39decf
--- /dev/null
+++ b/app/Controller/StoreController.php
@@ -0,0 +1,86 @@
+request->input('id');
+ if (empty($id)) {
+ return $this->result(1, [], 'id不能为空');
+ }
+ if ($this->request->isMethod('post')) {
+ $logo = $this->request->input('logo');
+ $name = $this->request->input('name');
+ $tel = $this->request->input('tel');
+ $address = $this->request->input('address');
+ $coordinates = $this->request->input('coordinates');
+ $capita = $this->request->input('capita');
+ $start_at = $this->request->input('start_at');
+ $announcement = $this->request->input('announcement');
+ $environment = $this->request->input('environment');
+
+ //>>1上传logo到阿里云oss
+ //>>2.上传商家环境到阿里云oss
+ //>>3.保存数据到数据库存
+ $fileNameLogo = $object = 'public/upload/' . date('Y') . '/' . date('m-d') . '/' . rand(0, 9999999999999999) . '.jpg';
+ $fileUpload = new FileUpload();
+ $resLogo = $fileUpload->ossUpload($logo, $fileNameLogo);
+ if (isset($resLogo['info']['http_code']) && $resLogo['info']['http_code'] == 200) {
+ $logo_url = $fileNameLogo;
+ } else {
+ return $this->result(1, []);
+ }
+ $environments = explode(',', $environment);
+ $envPaths = [];
+ foreach ($environments as $env) {
+ $fileNameEnv = $object = 'public/upload/' . date('Y') . '/' . date('m-d') . '/' . rand(0, 9999999999999999) . '.jpg';
+ $resEnv = $fileUpload->ossUpload($env, $fileNameEnv);
+ if (isset($resEnv['info']['http_code']) && $resLogo['info']['http_code'] == 200) {
+ $envPaths[] = $fileNameEnv;
+ }
+ }
+ if (count($envPaths)) {
+ $envPath = implode(',', $envPaths);
+ }
+
+ $res = Db::table('ims_cjdc_store')->where('id', $id)->update([
+ 'logo' => $logo_url ?? "",
+ 'name' => $name,
+ 'tel' => $tel,
+ 'address' => $address,
+ 'coordinates' => $coordinates,
+ 'capita' => $capita,
+ 'start_at' => $start_at,
+ 'announcement' => $announcement,
+ 'environment' => $envPath ?? "",
+ ]);
+ return $this->success($res);
+ }
+//'id','name','logo','tel','address','coordinates','capita','start_at','announcement','environment'
+ //获取店铺信息
+ $data = Db::table('ims_cjdc_store')
+ ->select(['id','name','logo','tel','address','coordinates','capita','start_at','announcement','environment'])
+ ->where('id',$id)
+ ->first();
+ if ($data){
+ $data->site = config('site_host');
+ }
+ return $this->success($data);
+
+
+
+ }
+}
\ No newline at end of file
diff --git a/app/Controller/TestController.php b/app/Controller/TestController.php
index f69cea8..10fe872 100644
--- a/app/Controller/TestController.php
+++ b/app/Controller/TestController.php
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Controller;
+use App\Libs\FeiePrintClient;
use Hyperf\HttpServer\Contract\RequestInterface;
use Hyperf\HttpServer\Annotation\AutoController;
use Hyperf\Utils\Coroutine;
@@ -22,6 +23,7 @@ class TestController extends AbstractController
{
private $name = 'default action';
+ protected $client = null;
public function index1(RequestInterface $request)
{
@@ -37,14 +39,33 @@ class TestController extends AbstractController
// $result = $client->handle("set");
- //$log = ApplicationContext::getContainer()->get(Log::class);
- $log = $this->log;
-
- $log->push(['test'=>1,'user_id'=>290,'msg'=>'order']);
- $log->event('t1',['test'=>1,'user_id'=>290,'msg'=>'order']);
+ // //$log = ApplicationContext::getContainer()->get(Log::class);
+ // $log = $this->log;
+ //
+ // $log->push(['test'=>1,'user_id'=>290,'msg'=>'order']);
+ // $log->event('t1',['test'=>1,'user_id'=>290,'msg'=>'order']);
+ //
+ // //$this->name = 'index1 action '. $result;
+ // return $this->name;
- //$this->name = 'index1 action '. $result;
- return $this->name;
+ $time = time();
+ $msgInfo = array(
+ 'user' => '13161443713@163.com',
+ 'stime' => $time,
+ 'sig' => sha1('13161443713@163.com' . 'XsaHzgePdyWTfcMX' . $time),
+ 'apiname' => 'Open_printMsg',
+ 'sn' => '920527381',
+ 'content' => '1111',
+ 'times' => 1//打印次数
+ );
+ $this->client = new FeiePrintClient('api.feieyun.cn', 80);
+ if (!$this->client->post('/Api/Open/', $msgInfo)) {
+ return '12';
+ } else {
+ //服务器返回的JSON字符串,建议要当做日志记录起来
+ $result = $this->client->getContent();
+ return $result;
+ }
}
public function index2(RequestInterface $request)
diff --git a/app/Exception/Handler/FilesystemExceptionHandler.php b/app/Exception/Handler/FilesystemExceptionHandler.php
new file mode 100644
index 0000000..f5f1b1c
--- /dev/null
+++ b/app/Exception/Handler/FilesystemExceptionHandler.php
@@ -0,0 +1,35 @@
+stopPropagation();
+
+ $content = json_encode([
+ "status" => 'error',
+ "code" => ErrorCode::UPLOAD_INVALID,
+ "result" => [],
+ "message" => $throwable->getMessage()
+ ]);
+
+ return $response->withHeader('Content-Type', 'application/json')
+ ->withStatus($throwable->status)
+ ->withBody(new SwooleStream($content));
+ }
+
+ public function isValid(Throwable $throwable): bool
+ {
+ return $throwable instanceof FilesystemException;
+ }
+}
\ No newline at end of file
diff --git a/app/Exception/Handler/ValidationExceptionHandler.php b/app/Exception/Handler/ValidationExceptionHandler.php
index cc2384c..cee5579 100644
--- a/app/Exception/Handler/ValidationExceptionHandler.php
+++ b/app/Exception/Handler/ValidationExceptionHandler.php
@@ -24,7 +24,7 @@ class ValidationExceptionHandler extends ExceptionHandler
]);
return $response->withHeader('Content-Type', 'application/json')
- ->withStatus($throwable->status)
+ ->withStatus(200)
->withBody(new SwooleStream($content));
}
diff --git a/app/Libs/FeiePrintClient.php b/app/Libs/FeiePrintClient.php
new file mode 100644
index 0000000..a1b4789
--- /dev/null
+++ b/app/Libs/FeiePrintClient.php
@@ -0,0 +1,307 @@
+host = $host;
+ $this->port = $port;
+ }
+ function get($path, $data = false) {
+ $this->path = $path;
+ $this->method = 'GET';
+ if ($data) {
+ $this->path .= '?'.$this->buildQueryString($data);
+ }
+ return $this->doRequest();
+ }
+ function post($path, $data) {
+ $this->path = $path;
+ $this->method = 'POST';
+ $this->postdata = $this->buildQueryString($data);
+ return $this->doRequest();
+ }
+ function buildQueryString($data) {
+ $querystring = '';
+ if (is_array($data)) {
+ foreach ($data as $key => $val) {
+ if (is_array($val)) {
+ foreach ($val as $val2) {
+ $querystring .= urlencode($key).'='.urlencode($val2).'&';
+ }
+ } else {
+ $querystring .= urlencode($key).'='.urlencode($val).'&';
+ }
+ }
+ $querystring = substr($querystring, 0, -1); // Eliminate unnecessary &
+ } else {
+ $querystring = $data;
+ }
+ return $querystring;
+ }
+ function doRequest() {
+ if (!$fp = @fsockopen($this->host, $this->port, $errno, $errstr, $this->timeout)) {
+ switch($errno) {
+ case -3:
+ $this->errormsg = 'Socket creation failed (-3)';
+ case -4:
+ $this->errormsg = 'DNS lookup failure (-4)';
+ case -5:
+ $this->errormsg = 'Connection refused or timed out (-5)';
+ default:
+ $this->errormsg = 'Connection failed ('.$errno.')';
+ $this->errormsg .= ' '.$errstr;
+ $this->debug($this->errormsg);
+ }
+ return false;
+ }
+ socket_set_timeout($fp, $this->timeout);
+ $request = $this->buildRequest();
+ $this->debug('Request', $request);
+ fwrite($fp, $request);
+ $this->headers = array();
+ $this->content = '';
+ $this->errormsg = '';
+ $inHeaders = true;
+ $atStart = true;
+ while (!feof($fp)) {
+ $line = fgets($fp, 4096);
+ if ($atStart) {
+ $atStart = false;
+ if (!preg_match('/HTTP\/(\\d\\.\\d)\\s*(\\d+)\\s*(.*)/', $line, $m)) {
+ $this->errormsg = "Status code line invalid: ".htmlentities($line);
+ $this->debug($this->errormsg);
+ return false;
+ }
+ $http_version = $m[1];
+ $this->status = $m[2];
+ $status_string = $m[3];
+ $this->debug(trim($line));
+ continue;
+ }
+ if ($inHeaders) {
+ if (trim($line) == '') {
+ $inHeaders = false;
+ $this->debug('Received Headers', $this->headers);
+ if ($this->headers_only) {
+ break;
+ }
+ continue;
+ }
+ if (!preg_match('/([^:]+):\\s*(.*)/', $line, $m)) {
+ continue;
+ }
+ $key = strtolower(trim($m[1]));
+ $val = trim($m[2]);
+ if (isset($this->headers[$key])) {
+ if (is_array($this->headers[$key])) {
+ $this->headers[$key][] = $val;
+ } else {
+ $this->headers[$key] = array($this->headers[$key], $val);
+ }
+ } else {
+ $this->headers[$key] = $val;
+ }
+ continue;
+ }
+ $this->content .= $line;
+ }
+ fclose($fp);
+ if (isset($this->headers['content-encoding']) && $this->headers['content-encoding'] == 'gzip') {
+ $this->debug('Content is gzip encoded, unzipping it');
+ $this->content = substr($this->content, 10);
+ $this->content = gzinflate($this->content);
+ }
+ if ($this->persist_cookies && isset($this->headers['set-cookie']) && $this->host == $this->cookie_host) {
+ $cookies = $this->headers['set-cookie'];
+ if (!is_array($cookies)) {
+ $cookies = array($cookies);
+ }
+ foreach ($cookies as $cookie) {
+ if (preg_match('/([^=]+)=([^;]+);/', $cookie, $m)) {
+ $this->cookies[$m[1]] = $m[2];
+ }
+ }
+ $this->cookie_host = $this->host;
+ }
+ if ($this->persist_referers) {
+ $this->debug('Persisting referer: '.$this->getRequestURL());
+ $this->referer = $this->getRequestURL();
+ }
+ if ($this->handle_redirects) {
+ if (++$this->redirect_count >= $this->max_redirects) {
+ $this->errormsg = 'Number of redirects exceeded maximum ('.$this->max_redirects.')';
+ $this->debug($this->errormsg);
+ $this->redirect_count = 0;
+ return false;
+ }
+ $location = isset($this->headers['location']) ? $this->headers['location'] : '';
+ $uri = isset($this->headers['uri']) ? $this->headers['uri'] : '';
+ if ($location || $uri) {
+ $url = parse_url($location.$uri);
+ return $this->get($url['path']);
+ }
+ }
+ return true;
+ }
+ function buildRequest() {
+ $headers = array();
+ $headers[] = "{$this->method} {$this->path} HTTP/1.0";
+ $headers[] = "Host: {$this->host}";
+ $headers[] = "User-Agent: {$this->user_agent}";
+ $headers[] = "Accept: {$this->accept}";
+ if ($this->use_gzip) {
+ $headers[] = "Accept-encoding: {$this->accept_encoding}";
+ }
+ $headers[] = "Accept-language: {$this->accept_language}";
+ if ($this->referer) {
+ $headers[] = "Referer: {$this->referer}";
+ }
+ if ($this->cookies) {
+ $cookie = 'Cookie: ';
+ foreach ($this->cookies as $key => $value) {
+ $cookie .= "$key=$value; ";
+ }
+ $headers[] = $cookie;
+ }
+ if ($this->username && $this->password) {
+ $headers[] = 'Authorization: BASIC '.base64_encode($this->username.':'.$this->password);
+ }
+ if ($this->postdata) {
+ $headers[] = 'Content-Type: application/x-www-form-urlencoded';
+ $headers[] = 'Content-Length: '.strlen($this->postdata);
+ }
+ $request = implode("\r\n", $headers)."\r\n\r\n".$this->postdata;
+ return $request;
+ }
+ function getStatus() {
+ return $this->status;
+ }
+ function getContent() {
+ return $this->content;
+ }
+ function getHeaders() {
+ return $this->headers;
+ }
+ function getHeader($header) {
+ $header = strtolower($header);
+ if (isset($this->headers[$header])) {
+ return $this->headers[$header];
+ } else {
+ return false;
+ }
+ }
+ function getError() {
+ return $this->errormsg;
+ }
+ function getCookies() {
+ return $this->cookies;
+ }
+ function getRequestURL() {
+ $url = 'https://'.$this->host;
+ if ($this->port != 80) {
+ $url .= ':'.$this->port;
+ }
+ $url .= $this->path;
+ return $url;
+ }
+ function setUserAgent($string) {
+ $this->user_agent = $string;
+ }
+ function setAuthorization($username, $password) {
+ $this->username = $username;
+ $this->password = $password;
+ }
+ function setCookies($array) {
+ $this->cookies = $array;
+ }
+ function useGzip($boolean) {
+ $this->use_gzip = $boolean;
+ }
+ function setPersistCookies($boolean) {
+ $this->persist_cookies = $boolean;
+ }
+ function setPersistReferers($boolean) {
+ $this->persist_referers = $boolean;
+ }
+ function setHandleRedirects($boolean) {
+ $this->handle_redirects = $boolean;
+ }
+ function setMaxRedirects($num) {
+ $this->max_redirects = $num;
+ }
+ function setHeadersOnly($boolean) {
+ $this->headers_only = $boolean;
+ }
+ function setDebug($boolean) {
+ $this->debug = $boolean;
+ }
+ function quickGet($url) {
+ $bits = parse_url($url);
+ $host = $bits['host'];
+ $port = isset($bits['port']) ? $bits['port'] : 80;
+ $path = isset($bits['path']) ? $bits['path'] : '/';
+ if (isset($bits['query'])) {
+ $path .= '?'.$bits['query'];
+ }
+ $client = new HttpClient($host, $port);
+ if (!$client->get($path)) {
+ return false;
+ } else {
+ return $client->getContent();
+ }
+ }
+ function quickPost($url, $data) {
+ $bits = parse_url($url);
+ $host = $bits['host'];
+ $port = isset($bits['port']) ? $bits['port'] : 80;
+ $path = isset($bits['path']) ? $bits['path'] : '/';
+ $client = new HttpClient($host, $port);
+ if (!$client->post($path, $data)) {
+ return false;
+ } else {
+ return $client->getContent();
+ }
+ }
+ function debug($msg, $object = false) {
+ if ($this->debug) {
+ print '
HttpClient Debug: '.$msg;
+ if ($object) {
+ ob_start();
+ print_r($object);
+ $content = htmlentities(ob_get_contents());
+ ob_end_clean();
+ print '
'.$content.'
';
+ }
+ print '
';
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/Libs/MQTTClient.php b/app/Libs/MQTTClient.php
new file mode 100644
index 0000000..0d08a87
--- /dev/null
+++ b/app/Libs/MQTTClient.php
@@ -0,0 +1,1098 @@
+setConnection($address, $port, $protocol)) {
+ ;
+ }
+ $this->packetId = rand(1,100)*100; // Reduce risk of creating duplicate ids in sequential sessions
+ }
+
+ /**
+ * Class destructor - Close socket
+ */
+ function __destruct(){
+ $this->close();
+ }
+
+ /**
+ * Setup conection parameters
+ *
+ * @param string $address
+ * @param string $port
+ * @param string $protocol
+ * @return boolean If return false then using default parameters where validation failed
+ */
+ function setConnection($address, $port=null, $protocol='tcp'){
+ $this->serverAddress = $address;
+ $this->serverPort = $port;
+
+ // Validate protocol
+ $protocol = strtolower($protocol);
+ if (($protocol != 'tcp') && !self::isEncryptionProtocol($protocol)) {
+ $this->debugMessage('Invalid protocol ('.$protocol.'). Setting to default (tcp).');
+ $this->protocol = 'tcp';
+ return false;
+ }
+ $this->protocol = $protocol;
+
+ return true;
+ }
+
+ /**
+ * Build url for connecting to stream
+ *
+ * @return string
+ */
+ private function getUrl() {
+ $url = '';
+ if ($this->protocol) $url .= $this->protocol .'://';
+ $url .= $this->serverAddress;
+ if ($this->serverPort) $url .= ':'. $this->serverPort;
+ return $url;
+ }
+
+ /**
+ * Check if encryption protocol is supported
+ *
+ * @param string $protcol
+ * @return boolean
+ */
+ private static function isEncryptionProtocol($protocol) {
+ return in_array(strtolower($protocol), ['ssl', 'tls', 'tlsv1.0', 'tlsv1.1', 'tlsv1.2', 'sslv3']);
+ }
+
+ /**
+ * Sets server certificate and protocol for ssl/tls encryption
+ *
+ * @param string $caFile CA file to identify server
+ * @param string $protocl Crypto protocol (See http://php.net/manual/en/migration56.openssl.php)
+ * @return boolean False if settings failed, else true
+ */
+ public function setEncryption($caFile, $protocol = null) {
+ if (file_exists($caFile)) {
+ $this->caFile = $caFile;
+ } else {
+ $this->debugMessage('CA file not found');
+ return false;
+ }
+ if(self::isEncryptionProtocol($protocol)) {
+ $this->protocol = $protocol;
+ } else if (!is_null($protocol)) {
+ $this->debugMessage('Unknown encryption protocol');
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Sets client crt and key files for client-side authentication
+ *
+ * @param string $crtFile Client certificate file
+ * @param string $keyFile Client key file
+ * @return boolean False if settings failed, else true
+ */
+ public function setClientEncryption($certificateFile, $keyFile) {
+ if (!file_exists($certificateFile)) {
+ $this->debugMessage('Client certificate file not found');
+ return false;
+ }
+ if (!file_exists($keyFile)) {
+ $this->debugMessage('Client key file not found');
+ return false;
+ }
+ $this->localCert= $certificateFile;
+ $this->localPrivateKey = $keyFile;
+ return true;
+ }
+
+ /**
+ * Set authentication details to be used when connecting
+ *
+ * @param string $username Username
+ * @param string $password Password
+ */
+ public function setAuthentication($username, $password) {
+ $this->connectUsername= $username;
+ $this->connectPassword = $password;
+ }
+
+ /**
+ * Set will (last message defined by MQTT) to send when connection is lost
+ *
+ * @param string $topic
+ * @param string $message
+ * @param integer $qos
+ * @param boolean $retain
+ */
+ public function setWill($topic, $message, $qos=1, $retain=false) {
+ $this->connectWill = true;
+ $this->connectWillQos = $qos;
+ $this->connectWillRetain = $retain;
+ $this->willTopic = $topic;
+ $this->willMessage = $message;
+ }
+
+ /**
+ * Connect to MQTT server
+ *
+ * @param string $clientId Unique id used by the server to identify the client
+ * @param boolean $cleanSession Set true to clear session on server, ie queued messages are purged (not recieved)
+ * @param integer $keepAlive Number of seconds a connection is considered to be alive without traffic
+ * @param integer $timeout Number of millliseconds before timeout when reading from socket
+ * @return boolean Returns false if connection failed
+ */
+ public function sendConnect($clientId, $cleanSession=false, $keepAlive=10, $timeout=5000) {
+
+ if (!$this->serverAddress) return false;
+
+ // Basic validation of clientid
+ // Note: A MQTT server may accept other chars and more than 23 chars in the clientid but that is optional,
+ // all chars below up to 23 chars are required to be accepted (see section "3.1.3.1 Client Identifier" of the standard)
+ if(preg_match("/[^0-9a-zA-Z]/",$clientId)) {
+ $this->debugMessage('ClientId can only contain characters 0-9,a-z,A-Z');
+ return false;
+ }
+ if(strlen($clientId) > 23) {
+ $this->debugMessage('ClientId max length is 23 characters/numbers');
+ return false;
+ }
+ $this->clientId = $clientId;
+
+ $this->connectCleanSession = $cleanSession;
+ $this->connectKeepAlive = $keepAlive;
+ $this->socketTimeout = $timeout;
+
+ // Setup certificates if encryption protocol selected
+ if ($this->isEncryptionProtocol($this->protocol)) {
+ $mozillaCiphers = implode(':', array(
+ 'ECDHE-RSA-AES128-GCM-SHA256',
+ 'ECDHE-ECDSA-AES128-GCM-SHA256',
+ 'ECDHE-RSA-AES256-GCM-SHA384',
+ 'ECDHE-ECDSA-AES256-GCM-SHA384',
+ 'DHE-RSA-AES128-GCM-SHA256',
+ 'DHE-DSS-AES128-GCM-SHA256',
+ 'kEDH+AESGCM',
+ 'ECDHE-RSA-AES128-SHA256',
+ 'ECDHE-ECDSA-AES128-SHA256',
+ 'ECDHE-RSA-AES128-SHA',
+ 'ECDHE-ECDSA-AES128-SHA',
+ 'ECDHE-RSA-AES256-SHA384',
+ 'ECDHE-ECDSA-AES256-SHA384',
+ 'ECDHE-RSA-AES256-SHA',
+ 'ECDHE-ECDSA-AES256-SHA',
+ 'DHE-RSA-AES128-SHA256',
+ 'DHE-RSA-AES128-SHA',
+ 'DHE-DSS-AES128-SHA256',
+ 'DHE-RSA-AES256-SHA256',
+ 'DHE-DSS-AES256-SHA',
+ 'DHE-RSA-AES256-SHA',
+ 'AES128-GCM-SHA256',
+ 'AES256-GCM-SHA384',
+ 'ECDHE-RSA-RC4-SHA',
+ 'ECDHE-ECDSA-RC4-SHA',
+ 'AES128',
+ 'AES256',
+ 'RC4-SHA',
+ 'HIGH',
+ '!aNULL',
+ '!eNULL',
+ '!EXPORT',
+ '!DES',
+ '!3DES',
+ '!MD5',
+ '!PSK'
+ ));
+ // Secure socket communication with these parameters, a ca-file is required
+ $options = [];
+ $options['verify_peer'] = true;
+ $options['verify_peer_name'] = true;
+ $options['verify_depth'] = 5;
+ $options['disable_compression'] = true;
+ $options['SNI_enabled'] = true;
+ $options['ciphers'] = $mozillaCiphers;
+ if($this->caFile) {
+ $options['cafile'] = $this->caFile;
+ }
+ if($this->localCert) {
+ $options['local_cert'] = $this->localCert;
+ if ($this->localPrivateKey) {
+ $options['local_pk'] = $this->localPrivateKey;
+ }
+ }
+ $socketContext = stream_context_create(['ssl' => $options]);
+ $this->debugMessage('Settings socket options: '. var_export($options, true));
+ } else {
+ $socketContext = null;
+ }
+
+ // Try to open socket
+ try {
+ $this->debugMessage('Opening socket to: '. $this->getUrl());
+ if ($socketContext) {
+ $this->socket = stream_socket_client($this->getUrl(), $errno, $errstr, 10, STREAM_CLIENT_CONNECT, $socketContext);
+ } else {
+ $this->socket = stream_socket_client($this->getUrl(), $errno, $errstr, 10, STREAM_CLIENT_CONNECT);
+ }
+ } catch (\ErrorException $error) {
+ $this->debugMessage('Exception: Could not open stream with error message: '. $error->getMessage());
+ $this->socket = null;
+ return false;
+ }
+
+ // Check if socket was opened successfully
+ if ($this->socket === false) {
+ $this->socket = null;
+ $this->debugMessage('Connection failed. Error-no:'. $errno .' Error message: '. $errstr);
+ return false;
+ }
+
+ // Set socket timeout
+ ini_set('default_socket_timeout', '10');
+ stream_set_timeout($this->socket, 0, $this->socketTimeout * 1000);
+ // Set stream to non-blocking mode, ie do not wait to read if stream is empty
+ stream_set_blocking($this->socket, true);
+
+ // Calculate connect flags to use in CONNECT header
+ $connectFlags = 0;
+ if ($this->connectCleanSession) $connectFlags += 0x02;
+ if ($this->connectWill) {
+ $connectFlags += 0x04;
+ if ($this->connectWillQos) $connectFlags += ($this->connectWill << 3);
+ if ($this->connectWillRetain) $connectFlags += 0x20;
+ }
+ if ($this->connectUsername) {
+ $connectFlags += 0x80;
+ if ($this->connectPassword) $connectFlags += 0x40;
+ }
+
+ // Build payload and header for CONNECT-packet
+ $payload = chr(0x00).chr(0x04); // MSB & LSB length of MQTT = 4
+ $payload .= 'MQTT';
+ $payload .= chr(0x04); // Protocol level (3.1.1)
+ $payload .= chr($connectFlags); // Connect flags
+ $payload .= chr($this->connectKeepAlive >> 8); // Keepalive (MSB)
+ $payload .= chr($this->connectKeepAlive & 0xff); // Keepalive (LSB)
+ if ($this->connectCleanSession && empty($this->clientId)) {
+ $this->clientId = rand(1,999999999);
+ }
+ if ($this->clientId) {
+ $payload .= $this->createPayload($this->clientId);
+ }
+ if($this->connectWill){
+ $payload .= $this->createPayload($this->willTopic);
+ $payload .= $this->createPayload($this->willMessage);
+ }
+ if($this->connectUsername) {
+ $payload .= $this->createPayload($this->connectUsername);
+ }
+ if ($this->connectPassword) {
+ $payload .= $this->createPayload($this->connectPassword);
+ }
+ $header = $this->createHeader(self::MQTT_CONNECT, $payload);
+ $this->debugMessage('Sending CONNECT');
+ $this->send($header . $payload);
+
+ // Wait for CONNACK packet
+ $response = $this->waitForPacket(self::MQTT_CONNACK);
+ if($response !== false && ($response[2] == chr(0))) {
+ $this->debugMessage('Connected to MQTT');
+ $this->lastConnectResult = 0;
+ return true;
+ } else {
+ $this->debugMessage('Connection failed! Error: '. ord($response[2]));
+ $this->lastConnectResult = ord($response[2]);
+ $this->close();
+ return false;
+ }
+ }
+
+ /**
+ * Publish a topic and message (QoS 0,1,2 supported)
+ *
+ * @param string $topic
+ * @param string $message
+ * @param byte $qos
+ * @return boolean
+ */
+ public function sendPublish($topic, $message, $qos = self::MQTT_QOS1, $retain = 0) {
+ if(!$this->isConnected()) return false;
+ if($qos!=self::MQTT_QOS0 && $qos!=self::MQTT_QOS1 && $qos!=self::MQTT_QOS2) return false;
+
+ $packetId = $this->getNextPacketId();
+ $payload = $this->createPayload($topic);
+ if($qos >= self::MQTT_QOS1) {
+ // Packet identifier required for QoS level >= 1
+ $payload .= $this->getPacketIdPayload();
+ }
+ $payload .= $message;
+
+ $dupFlag = 0;
+ $header = $this->createHeader(self::MQTT_PUBLISH + ($dupFlag<<3) + ($qos<<1) + $retain, $payload);
+ $this->debugMessage('Sending PUBLISH');
+ $this->send($header . $payload);
+
+ if($qos == self::MQTT_QOS1) {
+ // If QoS level 1, only a PUBACK packet is expected
+ $response = $this->waitForPacket(self::MQTT_PUBACK, $packetId);
+ if($response === false) {
+ $this->debugMessage('Packet missing, expecting PUBACK');
+ return false;
+ }
+ } elseif($qos == self::MQTT_QOS2) {
+ // If QoS level 2, a PUBREC packet is expected
+ $response = $this->waitForPacket(self::MQTT_PUBREC, $packetId);
+ if($response === false) {
+ $this->debugMessage('Packet missing, expecting PUBREC');
+ return false;
+ }
+
+ // Send PUBREL
+ $response = $this->sendPubRel($packetId);
+ if($response === false) {
+ $this->debugMessage('Failed to send PUBREL');
+ return false;
+ }
+
+ // A PUBCOMP packet is expected
+ $response = $this->waitForPacket(self::MQTT_PUBCOMP, $packetId);
+ if($response === false) {
+ $this->debugMessage('Packet missing, expecting PUBCOMP');
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Send PUBACK as response to a recieved PUBLISH packet (QoS Level 1)
+ *
+ * @param integer $packetId Packet identifier of PUBLISH packet
+ * @return boolean Returns true if packet sent successfully
+ */
+ public function sendPubAck($packetId) {
+ if(!$this->isConnected()) return false;
+
+ $payload = chr(($packetId & 0xff00)>>8) . chr($packetId & 0xff);
+ $header = $this->createHeader(self::MQTT_PUBACK, $payload);
+ $this->debugMessage('Sending PUBACK');
+ $this->send($header . $payload);
+
+ return true;
+ }
+
+ /**
+ * Send PUBREC as response to a recieved PUBLISH packet (QoS Level 2)
+ *
+ * @param integer $packetId Packet identifier of PUBLISH packet
+ * @return boolean Returns true if packet sent successfully
+ */
+ public function sendPubRec($packetId) {
+ if(!$this->isConnected()) return false;
+
+ $payload = chr(($packetId & 0xff00)>>8) . chr($packetId & 0xff);
+ $header = $this->createHeader(self::MQTT_PUBREC, $payload);
+ $this->debugMessage('Sending PUBREC');
+ $this->send($header . $payload);
+
+ return true;
+ }
+
+ /**
+ * Send PUBREL as response to a recieved PUBREC packet (QoS Level 2)
+ *
+ * @param integer $packetId Packet identifier of PUBLISH packet
+ * @return boolean Returns true if packet sent successfully
+ */
+ public function sendPubRel($packetId) {
+ if(!$this->isConnected()) return false;
+
+ $payload = chr(($packetId & 0xff00)>>8) . chr($packetId & 0xff);
+ $header = $this->createHeader(self::MQTT_PUBREL, $payload);
+ $this->debugMessage('Sending PUBREL');
+ $this->send($header . $payload);
+
+ return true;
+ }
+
+ /**
+ * Send PUBCOMP as response to a recieved PUBREL packet (QoS Level 2)
+ *
+ * @param integer $packetId Packet identifier of PUBLISH packet
+ * @return boolean Returns true if packet sent successfully
+ */
+ public function sendPubComp($packetId) {
+ if(!$this->isConnected()) return false;
+
+ $payload = chr(($packetId & 0xff00)>>8) . chr($packetId & 0xff);
+ $header = $this->createHeader(self::MQTT_PUBCOMP, $payload);
+ $this->debugMessage('Sending PUBCOMP');
+ $this->send($header . $payload);
+
+ return true;
+ }
+
+ /**
+ * Subscribe to topics with a quality of service
+ *
+ * @param string[] $topics Topics to subscribe for
+ * @param integer $qos Quality of serivce for all topics
+ * @return boolean Returns true if SUBACK was recieved
+ */
+ public function sendSubscribe($topics, $qos = self::MQTT_QOS1) {
+ if (!is_array($topics)) $topics = [$topics];
+ if(!$this->isConnected()) return false;
+
+ $packetId = $this->getNextPacketId();
+ $payload = $this->getPacketIdPayload();
+ foreach($topics as $topic) {
+ $payload .= $this->createPayload($topic);
+ $payload .= chr($qos);
+ }
+ $header = $this->createHeader(self::MQTT_SUBSCRIBE + 0x02, $payload);
+ $this->debugMessage('Sending SUBSCRIBE');
+ $this->send($header . $payload);
+
+ // A SUBACK packet is expected
+ $response = $this->waitForPacket(self::MQTT_SUBACK, $packetId);
+ if($response === false) {
+ $this->debugMessage('Packet missing, expecting SUBACK');
+ return false;
+ }
+ $responsePayload = substr($response, 3); // skip header and identifier (3 bytes)
+ if (strlen($responsePayload) != count($topics)) {
+ $this->debugMessage('Did not recieve SUBACK for all topics');
+ return false;
+ }
+
+ // Check which subscriptions that were approved
+ $topicsResult = [];
+ $i = 0;
+ foreach ($topics as $topic) {
+ $topicsResult[$topic] = [];
+ if ($responsePayload[$i] > 0x02) {
+ $topicsResult[$topic]['success'] = false;
+ $topicsResult[$topic]['qosGiven'] = null;
+ } else {
+ $topicsResult[$topic]['success'] = true;
+ $topicsResult[$topic]['qosGiven'] = (int) ord($responsePayload[$i]);
+ }
+ $i++;
+ }
+
+ return $topicsResult;
+ }
+
+ /**
+ * Send unsubscribe packet for given topics
+ *
+ * @param string[] $topics
+ * @return boolean Returns true if UNSUBACK was recieved
+ */
+ public function sendUnsubscribe($topics) {
+ if(!$this->isConnected()) return false;
+
+ $packetId = $this->getNextPacketId();
+ $payload = $this->getPacketIdPayload();
+ foreach($topics as $topic) {
+ $payload .= $this->createPayload($topic);
+ }
+ $header = $this->createHeader(self::MQTT_UNSUBSCRIBE + 0x02, $payload);
+ $this->debugMessage('Sending UNSUBSCRIBE');
+ $this->send($header . $payload);
+
+ // An UNSUBACK packet is expected
+ $response = $this->waitForPacket(self::MQTT_UNSUBACK, $packetId);
+ if($response === false) {
+ $this->debugMessage('Invalid packet received, expecting UNSUBACK');
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Sends PINGREQ packet to server
+ *
+ * @return boolean Returns true if PINGRESP was recieved
+ */
+ public function sendPing() {
+ if(!$this->isConnected()) return false;
+
+ $this->timeSincePingReq = time();
+ $header = $this->createHeader(self::MQTT_PINGREQ);
+ $this->debugMessage('Sending PING');
+ $this->send($header);
+ $this->pingReqTime = time();
+
+ // A PINGRESP packet is expected
+ $response = $this->waitForPacket(self::MQTT_PINGRESP);
+ if($response === false) {
+ $this->debugMessage('Invalid packet received, expecting PINGRESP');
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Send disconnect and close socket
+ */
+ public function sendDisconnect() {
+ if($this->isConnected()) {
+ $header = $this->createHeader(self::MQTT_DISCONNECT);
+ $this->debugMessage('Sending DISCONNECT');
+ $this->send($header);
+ $this->close();
+ }
+ }
+
+ /**
+ * Close socket
+ */
+ public function close() {
+ if($this->isConnected()) {
+ $this->debugMessage('Closing socket');
+ stream_socket_shutdown($this->socket, STREAM_SHUT_RDWR);
+ $this->socket = null;
+ $this->serverAliveTime = null;
+ }
+ }
+
+ /**
+ * Check if connected to stream
+ * @return boolean
+ */
+ public function isConnected() {
+ return !empty($this->socket);
+ }
+
+ /**
+ * Check if connection is alive
+ * @return boolean
+ */
+ public function isAlive() {
+ return $this->isConnected() && ($this->serverAliveTime + $this->connectKeepAlive <= time());
+ }
+
+ /**
+ * Set debug mode, if true then output progress messages
+ *
+ * @param boolean $mode
+ */
+ public function setDebug($mode = true) {
+ $this->debug = $mode;
+ }
+
+ /**
+ * Print message to console if debug mode is on
+ *
+ * @param string $message
+ */
+ private function debugMessage($message) {
+ if ($this->debug) {
+ echo 'MQTT: '. $message .PHP_EOL;
+ }
+ }
+
+ /**
+ * Return next packet identifier to use in MQTT packet
+ * Max 2 bytes to be used, restart on 0 if end reached
+ *
+ * @return integer
+ */
+ private function getNextPacketId() {
+ return ($this->packetId = ($this->packetId + 1) & 0xffff);
+ }
+
+ /**
+ * Return payload of packet id, use latest generated packet id as default
+ *
+ * @param integer $packetId
+ * @return string Two chars with apyload to add in MQTT-message
+ */
+ private function getPacketIdPayload($packetId = null) {
+ if (empty($packetId)) $packetId = $this->packetId;
+ return chr(($packetId & 0xff00)>>8) . chr($packetId & 0xff);
+ }
+
+ /**
+ * Add payload length as bytes to begining of string and return
+ *
+ * @param string $payload
+ * @return string
+ */
+ private function createPayload($payload) {
+ $fullLength = strlen($payload);
+ $retval = chr($fullLength>>8).chr($fullLength&0xff).$payload;
+ return $retval;
+ }
+
+ /**
+ * Decode payload using inital length (2 bytes) and return as string array
+ *
+ * @param string $payload
+ * @return string[]
+ */
+ private function decodePayload($payload) {
+ $result = [];
+ while (strlen($payload) >= 2) {
+ $length = ord($payload[0])<<8 + ord($payload[1]);
+ if (strlen($payload) <= $length + 2) {
+ $result[] = substr($payload, 2, $length);
+ }
+ $payload = substr($payload, min($length + 2, strlen($payload)));
+ }
+ return $result;
+ }
+
+ /**
+ * Send data to open socket
+ *
+ * @param string $data
+ * @return boolean Only returns true if all data was sent
+ */
+ private function send($data) {
+ if ($this->socket) {
+ $result = fwrite($this->socket, $data);
+ if (($result !== false) && ($result == strlen($data))) {
+ $this->serverAliveTime = time();
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Read bytes from socket until x bytes read, eof reached or socket timeout
+ *
+ * @param int $bytes Number of bytes to read
+ * @return string Return bytes read as a string
+ */
+ private function readBytes($bytes) {
+ if (!$this->socket) return false;
+ //if ($bytes == 0) return '';
+ $bytesLeft = $bytes;
+ $result = '';
+ do {
+ // If stream at end, close down socket and exit
+ if(feof($this->socket)) {
+ $this->debugMessage('Reached EOF for stream');
+ $this->close();
+ return $result;
+ }
+ // Try to read from stream
+ $str = fread($this->socket, $bytesLeft);
+ if ($str !== false && strlen($str) > 0) {
+ $result .= $str;
+ $bytesLeft -= strlen($str);
+ }
+ if ($bytesLeft <= 0) {
+ // If all bytes read, then return them
+ $this->serverAliveTime = time();
+ return $result;
+ }
+ // Check if timeout
+ $info = stream_get_meta_data($this->socket);
+ if ($info['timed_out']) {
+ $this->debugMessage('Read timeout');
+ return false;
+ }
+ // Wait a while before trying to read again (in micro seconds)
+ usleep($this->socketReadDelay * 1000);
+ } while (true);
+ }
+
+ /**
+ * Encode length to bytes to send in stream
+ *
+ * @param integer $len
+ * @return string
+ */
+ private function encodeLength($len) {
+ if ($len < 0 || $len >= 128*128*128*128) {
+ // illegal length
+ return false;
+ }
+ $output = '';
+ do {
+ $byte = $len & 0x7f; // keep lowest 7 bits
+ $len = $len >> 7; // shift away lowest 7 bits
+ if ($len > 0) {
+ $byte = $byte | 0x80; // set high bit to indicate continuation
+ }
+ $output .= chr($byte);
+ } while ($len > 0);
+ return $output;
+ }
+
+ /**
+ * Return length of packet by reading from stream
+ *
+ * @return integer
+ */
+ private function readPacketLength() {
+ $bytesRead = 0;
+ $len = 0;
+ $multiplier = 1;
+ do {
+ if ($bytesRead > 4) {
+ return false; // Malformed length
+ }
+ $str = $this->readBytes(1);
+ if ($str === false || strlen($str) != 1) {
+ return false; // Unexpected end of stream
+ }
+ $byte = ord($str[0]);
+ $len += ($byte & 0x7f) * $multiplier;
+ $isContinued = ($byte & 0x80);
+ if ($isContinued) {
+ $multiplier *= 128;
+ }
+ $bytesRead++;
+ } while ($isContinued);
+ return $len;
+ }
+
+ /**
+ * Create MQTT header from command and payload
+ *
+ * @param int $command Command to send
+ * @param string $payload Payload to be sent
+ *
+ * @return string Header to send
+ */
+ private function createHeader($command, $payload = '') {
+ return chr($command) . $this->encodeLength(strlen($payload));
+ }
+
+ /**
+ * Read next packet from stream
+ *
+ * @return boolean
+ */
+ private function readNextPacket() {
+ do {
+ $header = $this->readBytes(1);
+ if ($header === false) {
+ $this->lastReadStatus = self::READ_STATUS_ERROR_HEADER;
+ return false;
+ }
+ } while ((ord($header)&0xf0) == 0); // 0 is illegal control code to start with
+
+ $packetLength = $this->readPacketLength();
+ if ($packetLength === false) {
+ $this->debugMessage('Could not decode packet length');
+ $this->lastReadStatus = self::READ_STATUS_ERROR_PACKETLENGTH;
+ return false;
+ }
+
+ $payload = $packetLength > 0 ? $this->readBytes($packetLength) : '';
+ if ($payload === false) {
+ $this->lastReadStatus = self::READ_STATUS_ERROR_PAYLOAD;
+ return false;
+ }
+ $this->debugMessage('Packet response: '. self::str2hex($header . $payload));
+ $this->lastReadStatus = self::READ_STATUS_OK;
+ return $header . $payload;
+ }
+
+ public function getLastReadStatus() {
+ return $this->lastReadStatus;
+ }
+
+ public function hasMoreToRead() {
+ return ($this->lastReadStatus == self::READ_STATUS_OK) && $this->isConnected();
+ }
+
+ /**
+ * Read packets from stream and save to queue. Quit after x packets or timeout.
+ *
+ * @param integer $maxPackets Packet id the message must match
+ * @return integer Number of packets read
+ */
+ private function readPackets($maxPackets = 100) {
+ $receivedPackets = 0;
+ while (($receivedPackets < $maxPackets) && ($packet = $this->readNextPacket()) !== false) {
+ $this->packetQueue[] = $packet;
+ $receivedPackets++;
+ }
+ return $receivedPackets;
+ }
+
+ /**
+ * Wait until a certain packet is found in the stream.
+ * Save other recieved packets in queue.
+ *
+ * @param byte $header Header to look for (only 4 high bits) 0xf0
+ * @param integer $verifyPacketId Packet id the message must match
+ * @return boolean
+ */
+ private function waitForPacket($header, $verifyPacketId = false) {
+ // first check unhandled packets
+ foreach ($this->packetQueue as $key => $packet) {
+ if ($this->isPacketVerified($packet, $header, $verifyPacketId)) {
+ // if found, remove from queue and return packet
+ unset($this->packetQueue[$key]);
+ return $packet;
+ }
+ }
+ // if not found in queue, start reading from stream until found or timeout
+ do {
+ $packet = $this->readNextPacket();
+ if ($packet === false || empty($packet)) return false;
+ if ($this->isPacketVerified($packet, $header, $verifyPacketId)) {
+ return $packet;
+ }
+ // another packet found, save it to queue
+ $this->packetQueue[] = $packet;
+ } while(true);
+ }
+
+ /**
+ * Check if packet is of a given type and packet id match latest sent packet id
+ *
+ * @param string $packet
+ * @param char $header
+ * @param integer $verifyPacketId
+ * @return boolean
+ */
+ private function isPacketVerified($packet, $header, $verifyPacketId = false) {
+ if (is_string($packet) && strlen($packet) >= 1) {
+ if ((int)(ord($packet[0])&0xf0) == (int)($header&0xf0)) {
+ if ($verifyPacketId === false) return true;
+ if (strlen($packet) >= 3) {
+ $receivedPacketId = (int)(ord($packet[1])<<8) + ord($packet[2]);
+ if($verifyPacketId == $receivedPacketId) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Get packets matching a header from the queue and remove from queue
+ *
+ * @param char $header
+ * @return string[]
+ */
+ public function getQueuePackets($header) {
+ $foundPackets = [];
+ foreach ($this->packetQueue as $key => $packet) {
+ if ($this->isPacketVerified($packet, $header)) {
+ $foundPackets[] = $packet;
+ unset($this->packetQueue[$key]);
+ }
+ }
+ return $foundPackets;
+ }
+
+ /**
+ * Get PUBLISH packets and return them as messages
+ *
+ * @param integer $maxMessages Max messages to read
+ * @param boolean $sendPubAck If true, then send PUBACK to MQTT-server (QoS 1)
+ * @param boolean $sendPubRec If true, then send PUBREC to MQTT-server, wait for PUBREL and send PUBCOMP (QoS 2)
+ * @return string[] All PUBLISH messages which were confirmed or no confirmation needed/wanted
+ */
+ public function getPublishMessages($maxMessages = 100, $sendPubAck = false, $sendPubRec = false) {
+ $packetsRead = $this->readPackets($maxMessages);
+ $packets = $this->getQueuePackets(self::MQTT_PUBLISH);
+ $messages = [];
+ foreach ($packets as $key => $packet) {
+ $message = $this->decodePublish($packet);
+ if ($message === false) {
+ $this->debugMessage('Message could not be decoded');
+ continue;
+ }
+
+ if ($sendPubAck && ($message['qos'] == self::MQTT_QOS1)) {
+ if($this->sendPubAck($message['packetId']) === false) {
+ $this->debugMessage('Failed to send PUBACK');
+ continue;
+ }
+ } elseif ($sendPubRec && ($message['qos'] == self::MQTT_QOS2)) {
+ // Send PUBREC
+ if($this->sendPubRec($message['packetId']) === false) {
+ $this->debugMessage('Failed to send PUBREC');
+ continue;
+ }
+ // A PUBREL packet is expected
+ $response = $this->waitForPacket(self::MQTT_PUBREL, $message['packetId']);
+ if($response === false) {
+ $this->debugMessage('Packet missing, expecting PUBREL');
+ continue;
+ }
+ // Send PUBCOMP
+ if($this->sendPubComp($message['packetId']) === false) {
+ $this->debugMessage('Failed to send PUBCOMP');
+ continue;
+ }
+ }
+
+ // Package was successfully confirmed or no confirmation needed/wanted --> store it
+ $messages[] = $message;
+ }
+ return $messages;
+ }
+
+ /**
+ * Decode a publish packet to its attributes
+ *
+ * @param string $packet
+ * @return array|boolean Return message or false if decode failed
+ */
+ public function decodePublish($packet) {
+ if (!is_string($packet) || (strlen($packet) <= 3)) {
+ return false;
+ }
+ $flags = ord($packet[0]) & 0x0f;
+ $duplicate = ($flags == 0x80);
+ $retain = ($flags == 0x01);
+ $qos = ($flags>>1) & 0x03;
+ $topicLength = (ord($packet[1])<<8) + ord($packet[2]);
+ $topic = substr($packet, 3, $topicLength);
+
+ $payload = substr($packet, 3 + $topicLength); // Get the payload of the packet
+ if ($qos == 0) {
+ // no packet id for QoS 0, the payload is the message
+ $message = $payload;
+ $packetId = NULL;
+ } else {
+ if (strlen($payload) >= 2) {
+ $packetId = (ord($payload[0])<<8) + ord($payload[1]);
+ $message = substr($payload, 2); // skip packet id (2 bytes) for QoS 1 and 2
+ } else {
+ // 2 byte packet id required, but not found. exit gracefully (no failure)
+ $packetId = NULL;
+ $message = '';
+ }
+ }
+ return [
+ 'topic' => self::convertActiveMqTopic($topic),
+ 'message' => $message,
+ 'retain' => $retain,
+ 'duplicate' => $duplicate,
+ 'qos' => $qos,
+ 'packetId' => $packetId,
+ ];
+ }
+
+ /**
+ * Replace ActiveMQ special characters to MQTT-standard
+ *
+ * @param string $topic
+ * @return string
+ */
+ private static function convertActiveMqTopic($topic) {
+ $topic = str_replace(".","/", $topic);
+ $topic = str_replace("*","+", $topic);
+ $topic = str_replace(">","#", $topic);
+ return $topic;
+ }
+
+ /**
+ * Return a string interpreted as hex and ASCII (between 0x20-0x7f)
+ * Good for displaying recieved packets
+ *
+ * @param string $str
+ * @return string
+ */
+ private function str2hex($str) {
+ $hex = '';
+ $ascii = '';
+ for ($i=0; $i= 0x20 && ord($char) <= 0x7f) {
+ $ascii .= $char;
+ } else {
+ $ascii .= '.';
+ }
+ $hex .= dechex(ord($char)).' ';
+ }
+ return $hex . '"'. $ascii .'"';
+ }
+
+ public function dumpQueue() {
+ foreach ($this->packetQueue as $packet) {
+ $this->str2hex($packet) . PHP_EOL;
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/Model/Coupon.php b/app/Model/Coupon.php
index b6d984d..e50574b 100644
--- a/app/Model/Coupon.php
+++ b/app/Model/Coupon.php
@@ -6,6 +6,9 @@ use App\Model\Model;
class Coupon extends Model
{
+ const DISCOUNT_TYPE_CASH = 1;
+ const DISCOUNT_TYPE_RATE = 2;
+
protected $table = 'ims_system_coupon_user';
}
diff --git a/app/Model/CouponUserRec.php b/app/Model/CouponUserRec.php
new file mode 100644
index 0000000..c5c2608
--- /dev/null
+++ b/app/Model/CouponUserRec.php
@@ -0,0 +1,10 @@
+belongsTo(Goods::class, 'good_id', 'id');
+ }
+
+}
\ No newline at end of file
diff --git a/app/Model/Store.php b/app/Model/Store.php
new file mode 100644
index 0000000..db4805a
--- /dev/null
+++ b/app/Model/Store.php
@@ -0,0 +1,9 @@
+ 'required|nonempty|file|ext_not_in',
+ 'type' => 'nonempty|alpha'
+ ];
+ }
+
+ public function messages(): array
+ {
+ return [
+ 'upload.required' => '未选择上传的文件',
+ 'upload.nonempty' => '文件异常',
+ 'upload.file' => '文件上传不成功',
+ 'upload.ext_not_in' => '文件不允许上传',
+ 'type.nonempty' => '文件类型参数异常',
+ ];
+ }
+
+ public function attributes(): array
+ {
+ return [
+ 'upload' => '文件'
+ ];
+ }
+}
diff --git a/app/Request/BaseFormRequest.php b/app/Request/BaseFormRequest.php
new file mode 100644
index 0000000..14bbc31
--- /dev/null
+++ b/app/Request/BaseFormRequest.php
@@ -0,0 +1,26 @@
+withStatus(200);
+ // }
+}
\ No newline at end of file
diff --git a/app/Request/ImageBase64Request.php b/app/Request/ImageBase64Request.php
new file mode 100644
index 0000000..88a752a
--- /dev/null
+++ b/app/Request/ImageBase64Request.php
@@ -0,0 +1,46 @@
+ 'required|nonempty|base64:image',
+ 'type' => 'nonempty|alpha'
+ ];
+ }
+
+ public function messages(): array
+ {
+ return [
+ 'upload.required' => '未选择上传的文件',
+ 'upload.base64' => '文件不是正常的图片',
+ 'upload.nonempty' => '文件异常',
+ 'type.nonempty' => '图片类型参数异常',
+ ];
+ }
+
+ public function attributes(): array
+ {
+ return [
+ 'upload' => '图片'
+ ];
+ }
+}
diff --git a/app/Request/ImageRequest.php b/app/Request/ImageRequest.php
new file mode 100644
index 0000000..85bad47
--- /dev/null
+++ b/app/Request/ImageRequest.php
@@ -0,0 +1,46 @@
+ 'required|nonempty|file|image',
+ 'type' => 'nonempty|alpha'
+ ];
+ }
+
+ public function messages(): array
+ {
+ return [
+ 'upload.required' => '未选择上传的文件',
+ 'upload.image' => '文件不是正常的图片',
+ 'upload.nonempty' => '文件异常',
+ 'type.nonempty' => '图片类型参数异常',
+ ];
+ }
+
+ public function attributes(): array
+ {
+ return [
+ 'upload' => '图片'
+ ];
+ }
+}
diff --git a/app/Request/OrderOfflineRequest.php b/app/Request/OrderOfflineRequest.php
new file mode 100644
index 0000000..b5db3ce
--- /dev/null
+++ b/app/Request/OrderOfflineRequest.php
@@ -0,0 +1,42 @@
+ 'required|nonempty|integer',
+ 'user_id' => 'required|nonempty|integer',
+ 'money' => 'required|nonempty',
+ ];
+ }
+
+ public function messages(): array
+ {
+ return [
+ '*.*' => ':attribute 参数异常'
+ ];
+ }
+
+ public function attributes(): array
+ {
+ return [
+
+ ];
+ }
+}
diff --git a/app/Request/OrderOnlineRequest.php b/app/Request/OrderOnlineRequest.php
new file mode 100644
index 0000000..019cc99
--- /dev/null
+++ b/app/Request/OrderOnlineRequest.php
@@ -0,0 +1,70 @@
+ 'nonempty',
+ 'delivery_no' => '',
+ 'dada_fee' => 'nonempty',
+ 'market_id' => 'required|nonempty|integer',
+ 'user_id' => 'required|nonempty|integer',
+ 'money' => 'required|nonempty',
+ 'box_money' => '',
+ 'ps_money' => '',
+ 'mj_money' => '',
+ 'xyh_money' => '',
+ 'yhq_money' => '',
+ 'yhq_money2' => '',
+ 'zk_money' => '',
+ 'tel' => 'required|nonempty',
+ 'name' => 'required|nonempty',
+ 'address' => 'required|nonempty',
+ 'area' => '',
+ 'lat' => 'required|nonempty',
+ 'lng' => 'required|nonempty',
+ 'note' => '',
+ 'type' => 'required|nonempty',
+ 'form_id' => '',
+ 'form_id2' => '',
+ 'delivery_time' => '',
+ 'order_type' => 'nonempty',
+ 'pay_type' => 'nonempty',
+ 'coupon_id' => '',
+ 'coupon_id2' => '',
+ 'uniacid' => 'nonempty',
+ 'store_list' => 'nonempty',
+ 'receive_coupon_ids' => '',
+ ];
+ }
+
+ public function messages(): array
+ {
+ return [
+ '*.*' => ':attribute 参数异常'
+ ];
+ }
+
+ public function attributes(): array
+ {
+ return [
+
+ ];
+ }
+}
diff --git a/app/Request/WxminiPayRequest.php b/app/Request/WxminiPayRequest.php
new file mode 100644
index 0000000..633a9f5
--- /dev/null
+++ b/app/Request/WxminiPayRequest.php
@@ -0,0 +1,38 @@
+ 'required|nonempty|integer',
+ 'openid' => 'required|nonempty',
+ 'money' => 'required|nonempty',
+ ];
+ }
+
+ public function messages(): array
+ {
+ return [
+ '*.*' => ':attribute 参数异常',
+ ];
+ }
+
+ public function attributes(): array
+ {
+ return [
+ 'order_id' => '订单',
+ 'money' => '订单金额',
+ 'openid' => '用户标识',
+ ];
+ }
+}
\ No newline at end of file
diff --git a/app/Service/AttachmentService.php b/app/Service/AttachmentService.php
new file mode 100644
index 0000000..65ab9a4
--- /dev/null
+++ b/app/Service/AttachmentService.php
@@ -0,0 +1,73 @@
+getRealPath();
+ $fileHash = md5_file($fileRealPath);
+
+ $path = $this->getBasePath($path, $attachmenttype);
+ $fileName = $path . '/' . $fileHash . '.' . $file->getExtension();
+
+ $stream = fopen($fileRealPath, 'r+');
+ $filesystem->writeStream($fileName, $stream);
+ fclose($stream);
+
+ return $fileName;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function base64Upload($contents, $path, $filesystem)
+ {
+
+ preg_match('/^(data:\s*image\/(\w+);base64,)/', $contents, $result);
+ if (empty($result)) {
+ throw new FilesystemNotFoundException(ErrorCode::getMessage(ErrorCode::UPLOAD_INVALID),ErrorCode::UPLOAD_INVALID);
+ }
+
+ $contents = base64_decode(str_replace($result[1], '', $contents));
+
+ $fileHash = md5($contents);
+ $path = $this->getBasePath($path);
+ $fileName = $path . '/' . $fileHash . '.' . $result[2];
+
+ $filesystem->write($fileName, $contents);
+
+ return $fileName;
+ }
+
+ protected function getBasePath($path, $attachmenttype = 'image')
+ {
+ switch ($attachmenttype) {
+ case 'image':
+ $baseDir = env('IMAGE_BASE', '/attachment/images');
+ break;
+
+ case 'file':
+ $baseDir = env('FILES_BASE', '/attachment/files');
+ break;
+
+ default:
+ $baseDir = env('FILES_BASE', '/attachment');
+ break;
+ }
+
+ $path = $path ? '/'.$path : '';
+ $path .= '/'.date('Y').'/'.date('m').'/'.date('d');
+ return $baseDir.$path;
+ }
+}
\ No newline at end of file
diff --git a/app/Service/AttachmentServiceInterface.php b/app/Service/AttachmentServiceInterface.php
new file mode 100644
index 0000000..28439b2
--- /dev/null
+++ b/app/Service/AttachmentServiceInterface.php
@@ -0,0 +1,23 @@
+get(Redis::class);
+ $couponTodayUsedIds = $redis->sMembers('coupon_'.date('Ymd').'_used_'.$userId);
+ $currentTime = time();
+
+ $builder = Db::table('ims_system_coupon_user_receive as receive')
+ ->join('ims_system_coupon_user as coupon', 'coupon.id', '=', 'receive.system_coupon_user_id', 'inner');
+
+ if (is_array($fields)&&!empty($fields)) {
+ $builder->select($fields);
+ }
+
+ if (is_array($couponTodayUsedIds)&&!empty($couponTodayUsedIds)) {
+ $builder->whereNotIn('coupon.id', $couponTodayUsedIds);
+ }
+
+ return $builder->where(['receive.user_id' => $userId])
+ ->whereIn('receive.status', [0,1])
+ ->where('receive.number_remain', '>', 0)
+ ->whereIn('coupon.type', [1,$type])
+ ->where('coupon.full_amount', '<=', $orderAmount)
+ ->where('coupon.usable_start_time', '<=', $currentTime)
+ ->where('coupon.usable_end_time', '>=', $currentTime)
+ ->where('coupon.usable_number', '<=', Db::raw('receive.number_remain'))
+ ->where('coupon.market_id', 'in', [0, $marketId])
+ ->whereIn('coupon.storetype_id', $storeTypeIds)
+ ->orderByRaw('coupon.discounts DESC, coupon.full_amount DESC')
+ ->get()
+ ->toArray();
+ }
+
+ /**
+ * 缓存优惠券今日使用情况
+ * @param $userId
+ * @param $couponId
+ * @param $couponRecId
+ * @return bool
+ */
+ function cacheTodayCouponUsed($userId, $couponId, $couponRecId)
+ {
+
+ $redis = ApplicationContext::getContainer()->get(Redis::class);
+
+ $setRes = $redis->sAdd(
+ 'coupon_'.date('Ymd').'_used_'.$userId,
+ $couponId
+ );
+
+ $expireRes = $redis->expire(
+ 'coupon_'.date('Ymd').'_used_'.$userId,
+ strtotime(date('Y-m-d').' 23:59:59')-time()
+ );
+
+ return $setRes&&$expireRes;
+ }
+
}
\ No newline at end of file
diff --git a/app/Service/CouponServiceInterface.php b/app/Service/CouponServiceInterface.php
index b096ecd..bdd60c5 100644
--- a/app/Service/CouponServiceInterface.php
+++ b/app/Service/CouponServiceInterface.php
@@ -15,4 +15,16 @@ interface CouponServiceInterface
public function getUserAvailableCoupons();
-}
\ No newline at end of file
+ /**
+ * 当前订单可用优惠券列表
+ * @param $orderAmount 订单金额
+ * @param $marketId 市场ID
+ * @param $userId 用户ID
+ * @param int $type 优惠券类型,1全平台 2线上 3线下
+ * @param int[] $storeTypeIds 商户类型ID数组
+ * @param array $fields
+ * @return mixed
+ */
+ public function getOrderCanUseCoupons($orderAmount, $marketId, $userId, $fields=[], $type=1, $storeTypeIds=[0]);
+
+}
diff --git a/app/Service/DeviceServiceImp.php b/app/Service/DeviceServiceImp.php
new file mode 100644
index 0000000..16c9110
--- /dev/null
+++ b/app/Service/DeviceServiceImp.php
@@ -0,0 +1,136 @@
+where(['store_id' => $store_id, 'is_bind' => SpeakerDevic::IS_BIND_YES])->get()->toArray();
+ }
+
+ /**
+ * 绑定
+ * @param $dev_name
+ * @param $store_id
+ * @return SpeakerDevic|null
+ * @throws \Throwable
+ */
+ public function bindByStoreId($dev_name, $store_id)
+ {
+ $sd = null;
+
+ if ($this->checkDeviceEnable($dev_name)) {
+ return $sd;
+ }
+
+ try {
+
+ // 获取市场ID
+ $market_id = Store::query()->where(['id' => $store_id])->value('market_id');
+
+ $sd = SpeakerDevic::query()->updateOrCreate(
+ ['store_id' => $store_id, 'device_name' => $dev_name],
+ ['market_id' => $market_id, 'bind_time' => time(), 'is_bind' => SpeakerDevic::IS_BIND_YES]
+ );
+
+ } catch (Exception $e) {
+ $this->log->event(LogLabel::DEVICE_LOG, ['msg' => '绑定设备异常:'.$e->getMessage()]);
+ }
+ return $sd;
+ }
+
+ /**
+ * 解绑
+ * @param $bind_id
+ * @return int
+ */
+ public function unbindById($bind_id)
+ {
+ return SpeakerDevic::query()->where(['id' => $bind_id])->update(['is_bind' => SpeakerDevic::IS_BIND_NO]);
+ }
+
+ /**
+ * 发布语音消息
+ * @param $dev_names
+ * @param $msg
+ * @return bool
+ */
+ public function pubMsgToStoreByDevName($dev_names, $msg)
+ {
+ foreach ($dev_names as $key => $dev_name) {
+ $this->IOTService->pub($dev_name['device_name'], $msg);
+ }
+
+ return true;
+ }
+
+ public function pubMsgToStoreByOrderMainId($order_id, $is_main = true)
+ {
+
+ // 获取订单
+ $orders = Order::query()->select(['id','order_num','money', 'pay_type', 'store_id', 'type']);
+ if ($is_main) {
+ $orders = $orders->where(['order_main_id' => $order_id])->get()->toArray();
+ } else {
+ $orders = $orders->where(['id' => $order_id])->get()->toArray();
+ }
+
+ if(empty($orders)) return;
+
+ // 循环发送
+ foreach ($orders as $k => &$order) {
+
+ $device_names = SpeakerDevic::query()
+ ->select(['device_name'])
+ ->where(['store_id' => $order['store_id'], 'is_bind' => SpeakerDevic::IS_BIND_YES])
+ ->get()
+ ->toArray();
+
+ $msg = "{\"msg\":\"到账".$order['money']."元\"}";
+ foreach ($device_names as $key => $dev_name) {
+ $this->IOTService->pub($dev_name['device_name'], $msg);
+ }
+ }
+
+ return true;
+
+ }
+
+ /**
+ * 当前设备是否已经被绑定
+ * @param $dev_name
+ * @return bool
+ */
+ protected function checkDeviceEnable($dev_name)
+ {
+ return SpeakerDevic::query()->where(['device_name' => $dev_name, 'is_bind' => SpeakerDevic::IS_BIND_YES])->exists();
+ }
+
+}
\ No newline at end of file
diff --git a/app/Service/DeviceServiceInterface.php b/app/Service/DeviceServiceInterface.php
new file mode 100644
index 0000000..68cd47c
--- /dev/null
+++ b/app/Service/DeviceServiceInterface.php
@@ -0,0 +1,12 @@
+join('ims_cjdc_order as o','o.order_main_id', '=', 'm.id','inner')
+ ->join('ims_cjdc_order_goods as g','o.id','=', 'g.order_id','inner')
+ ->join('ims_cjdc_feprint as f','m.market_id','=', 'f.market_id','inner')
+ ->join('ims_cjdc_store as s','s.id','=', 'o.store_id','inner')
+ ->where('m.global_order_id', $order_num)
+ ->selectRaw("o.note as o_note,g.name,g.number,g.money,g.good_unit,m.delivery_time as ps_time,m.address,m.note,m.name as user_name,m.dada_fee,m.money as m_money,m.yhq_money2,m.box_money,f.sn,m.tel,m.order_num,g.id,g.spec,s.name as shopname")
+ ->orderBy('s.id')
+ ->get()
+ ->toArray();
+ foreach ($data as $key => &$item) {
+ $item = (array)$item;
+ }
+
+ $content = $this->printFormat($data, 14, 6, 3, 6);
+ $res = $this->printMsg($data[0]['sn'], $content, 1);
+ return ($res);
+ }
+
+ /**
+ * [打印订单接口 Open_printMsg]
+ * @param [string] $sn [打印机编号sn]
+ * @param [string] $content [打印内容]
+ * @param [string] $times [打印联数]
+ * @return [string] [接口返回值]
+ */
+ protected function printMsg($sn, $content, $times = 1)
+ {
+ $time = time(); //请求时间
+ $msgInfo = array(
+ 'user' => self::USER,
+ 'stime' => $time,
+ 'sig' => sha1(self::USER . self::UKEY . $time),
+ 'apiname' => 'Open_printMsg',
+ 'sn' => $sn,
+ 'content' => $content,
+ 'times' => $times//打印次数
+ );
+
+ $client = new FeiePrintClient(self::IP, self::PORT);
+ if (!$client->post(self::PATH, $msgInfo)) {
+ echo 'error';
+ } else {
+ // 服务器返回的JSON字符串,建议要当做日志记录起来
+ $result = $client->getContent();
+ return $result;
+ }
+ }
+
+ protected function printFormat($arr, $A, $B, $C, $D)
+ {
+ $orderInfo = '懒族生活
';
+ $orderInfo .= '名称 单价 数量 金额
';
+ $orderInfo .= '--------------------------------
';
+ $shopname = "";
+ $shopnum = 0;
+ foreach ($arr as $k5 => $v5) {
+ if ($shopname != $v5['shopname']) {
+ if ($shopname != "") {
+ $orderInfo .= '
';
+ }
+ $shopnum++;
+ $orderInfo .= "(" . $shopnum . ")" .$v5['shopname'] . '
';
+ $shopname = $v5['shopname'];
+ }
+ $name = $v5['name'];
+ if(!empty($v5['spec'])) {
+ $name .= "(规格:". $v5['spec'].")";
+ }elseif (!empty($v5['good_unit'])){
+ $name .= "(规格:". $v5['good_unit'].")";
+ }
+ $price = $v5['money'];
+ $num = $v5['number'];
+ $prices = sprintf("%.2f",$v5['money']*$v5['number']);
+ $kw3 = '';
+ $kw1 = '';
+ $kw2 = '';
+ $kw4 = '';
+ $str = $name;
+ $blankNum = $A;//名称控制为14个字节
+ $lan = mb_strlen($str,'utf-8');
+ $m = 0;
+ $j=1;
+ $blankNum++;
+ $result = array();
+ if(strlen($price) < $B){
+ $k1 = $B - strlen($price);
+ for($q=0;$q<$k1;$q++){
+ $kw1 .= ' ';
+ }
+ $price = $kw1.$price;
+ }
+ if(strlen($num) < $C){
+ $k2 = $C - strlen($num);
+ for($q=0;$q<$k2;$q++){
+ $kw2 .= ' ';
+ }
+ $num = $kw2.$num;
+ }
+ if(strlen($prices) < $D){
+ $k3 = $D - strlen($prices);
+ for($q=0;$q<$k3;$q++){
+ $kw4 .= ' ';
+ }
+ $prices = $kw4.$prices;
+ }
+ for ($i=0;$i<$lan;$i++){
+ $new = mb_substr($str,$m,$j,'utf-8');
+ $j++;
+ if(mb_strwidth($new,'utf-8')<$blankNum) {
+ if($m+$j>$lan) {
+ $m = $m+$j;
+ $tail = $new;
+ // $lenght = iconv("UTF-8", "GBK//IGNORE", $new);
+ $k = $A - mb_strwidth($new,'utf-8');
+ for($q=0;$q<$k;$q++){
+ $kw3 .= ' ';
+ }
+ if($m==$j){
+ $tail .= $kw3.' '.$price.' '.$num.' '.$prices;
+ }else{
+ $tail .= $kw3.'
';
+ }
+ break;
+ }else{
+ $next_new = mb_substr($str,$m,$j,'utf-8');
+ if(mb_strwidth($next_new,'utf-8')<$blankNum) continue;
+ else{
+ $m = $i+1;
+ $result[] = $new;
+ $j=1;
+ }
+ }
+ }
+ }
+ $head = '';
+ foreach ($result as $key=>$value) {
+ if($key < 1){
+ // $v_lenght = iconv("UTF-8", "GBK//IGNORE", $value);
+ $v_lenght = mb_strwidth($value,'utf-8');
+ if($v_lenght == 13) $value = $value." ";
+ $head .= $value.' '.$price.' '.$num.' '.$prices;
+ }else{
+ $head .= $value.'
';
+ }
+ }
+ $orderInfo .= $head.$tail;
+ if(!empty($v5['o_note'])){
+ $orderInfo .= '备注:'.$v5['o_note'].'
';
+ }
+ }
+ // $time = date('Y-m-d H:i:s', time());
+ $orderInfo .= '--------------------------------
';
+ if ($arr[0]['box_money'] > 0) {
+ $kw5 = '';
+ $len = 24 - strlen($arr[0]['box_money']);
+ for ($q = 0; $q < $len; $q++) {
+ $kw5 .= ' ';
+ }
+ $orderInfo .= '包装费:' . $kw5 . $arr[0]['box_money'] . '
';
+ }
+ if($arr[0]['dada_fee'] > 0){
+ $kw5 = '';
+ $len = 24 - strlen($arr[0]['dada_fee']);
+ for ($q = 0; $q < $len; $q++) {
+ $kw5 .= ' ';
+ }
+ $orderInfo .= '配送费:'.$kw5.$arr[0]['dada_fee'].'
';
+ }
+ if($arr[0]['yhq_money2'] > 0){
+ $yhq_money2 = sprintf("%.2f",$arr[0]['yhq_money2']);
+ $kw6 = '';
+ $len = 25 - strlen($yhq_money2);
+ for ($q = 0; $q < $len; $q++) {
+ $kw6 .= ' ';
+ }
+ $orderInfo .= '红包:'.$kw6.'-'.$yhq_money2.'
';
+ }
+ $total = '合计:'.$arr[0]['m_money'];
+ $user_name = $arr[0]['user_name'];
+ if(strlen($user_name)>18){
+ $user_name=substr($user_name,0,18).'...';
+ }
+ $str = $user_name . $total;
+ $kw5 = '';
+ // $lenght = iconv("UTF-8", "GBK//IGNORE", $str);
+ $total_len = 32 - mb_strwidth($str,'utf-8');
+ for ($q = 0; $q < $total_len; $q++) {
+ $kw5 .= ' ';
+ }
+ $total_str = $user_name.$kw5.$total;
+ $orderInfo .= $total_str.'
';
+ $orderInfo .= '送货地点:' . $arr[0]['address'] . '
';
+ $tel = substr_replace( $arr[0]['tel'], '****', 3, 4);
+ $orderInfo .= '联系电话:' . $tel . '
';
+ $orderInfo .= '配送时间:' . $arr[0]['ps_time'] . '
';
+ if(!empty($arr[0]['note'])){
+ $orderInfo .= '备注:'.$arr[0]['note'].'
';
+ }
+ //$orderInfo .= 'http://www.feieyun.com';//把解析后的二维码生成的字符串用标签套上即可自动生成二维码
+ return $orderInfo;
+ }
+}
\ No newline at end of file
diff --git a/app/Service/FeiePrintServiceInterface.php b/app/Service/FeiePrintServiceInterface.php
new file mode 100644
index 0000000..5e67cdc
--- /dev/null
+++ b/app/Service/FeiePrintServiceInterface.php
@@ -0,0 +1,10 @@
+regionId(env('ALI_IOT_REGION'))
+ ->asDefaultClient();
+
+ try {
+ AlibabaCloud::rpc()
+ ->product('Iot')
+ ->version('2018-01-20')
+ ->action('Pub')
+ ->method('POST')
+ ->host(env('ALI_IOT_HOST'))
+ ->options([
+ 'query' => [
+ 'RegionId' => "cn-shanghai",
+ 'TopicFullName' => "/".env('ALI_IOT_PRODUCT_KEY')."/".$device_name."/user/get",
+ 'MessageContent' => base64_encode($msg),
+ 'ProductKey' => env('ALI_IOT_PRODUCT_KEY'),
+ ],
+ ])
+ ->request();
+ } catch (ClientException $e) {
+ $this->log->event(LogLabel::DEVICE_LOG, ['msg' => 'ClientException发布失败:'.$e->getErrorMessage()]);
+ return false;
+ } catch (ServerException $e) {
+ $this->log->event(LogLabel::DEVICE_LOG, ['msg' => 'ServerException发布失败:'.$e->getErrorMessage()]);
+ return false;
+ }
+
+ return true;
+ }
+
+}
\ No newline at end of file
diff --git a/app/Service/IOTServiceInterface.php b/app/Service/IOTServiceInterface.php
new file mode 100644
index 0000000..931aca6
--- /dev/null
+++ b/app/Service/IOTServiceInterface.php
@@ -0,0 +1,8 @@
+ '微信支付', '2' => '余额支付', '3' => '积分支付', '4' => '货到付款'];
+ $address_store = $order['address'] . ';' .$order['name']. ';'. substr_replace($order['tel'],'****',3,4);
+ $address = $order['address'] . ';' .$order['name']. ';'. $order['tel'];
+
+ // 查询子订单,用于发消息给商户
+ $order_children = Order::query()->select(['id', 'order_num', 'store_id', 'money', 'time'])
+ ->where(['order_main_id' => $order_main_id])
+ ->get()
+ ->toArray();
+
+ $goods_temp_all = [];
+ foreach ($order_children as $key => &$item) {
+
+ // 订单商品
+ $order_goods = OrderGoods::query()->select(['name', 'number', 'spec', 'good_unit'])
+ ->where(['order_id' => $item['id']])
+ ->get()
+ ->toArray();
+
+ $goods_temp = [];
+ foreach ($order_goods as $k => &$goods) {
+ array_push($goods_temp, $goods['name']."*".$goods['number']."/".($goods['spec']?:$goods['good_unit']));
+ array_push($goods_temp_all, $goods['name']."*".$goods['number']."/".($goods['spec']?:$goods['good_unit']));
+ }
+
+ // 商户/门店的openid
+ $store = Store::query()->select(['id', 'name', 'user_id'])
+ ->where(['id' => $item['store_id']])
+ ->first()->toArray();
+ $store['openid'] = Users::query()
+ ->where(['id' => $store['user_id']])
+ ->value('openid');
+
+ // 模板数据
+ $data_store = [
+ 'first' => ['您有新的外卖订单!订单编号:'.$item['order_num'], '#ff0000'],
+ 'keyword' => [
+ ["您的外卖订单详情:\r\n".implode(";\r\n",$goods_temp), '#ff0000'],
+ $item['money'],
+ $payTypes[$order['pay_type']],
+ $item['time']?:'',
+ $address_store,
+ ],
+ 'remark' => [$order['note'], '#4e6ef2']
+ ];
+
+ $ret_store = $this->sendTempMsg($store['openid'], '-M7DG_ACwJxqdAvyvJuAnPpx4xaLf3VkkN0fckno71c',$data_store);
+ }
+
+ // 模板数据发送消息给用户
+ $data_user = [
+ 'first' => '您好,下单成功!订单编号:'.$order['order_num'],
+ 'keyword' => [
+ implode(";\r\n", $goods_temp_all),
+ $order['money'],
+ $payTypes[$order['pay_type']],
+ date('Y-m-d H:i:s', $order['time_add']),
+ $address,
+ ],
+ 'remark' => '感谢您的光临,欢迎下次再来!'
+ ];
+
+ // 获取用户openid,发送给用户
+ $user_openid = Users::query()->where(['id' => $order['user_id']])->value('openid');
+ $ret_user = $this->sendTempMsg($user_openid,'-M7DG_ACwJxqdAvyvJuAnPpx4xaLf3VkkN0fckno71c', $data_user);
+
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function sendTemMsgForOfflineOrder($order_main_id)
+ {
+
+ // 查询子订单,用于发消息给商户
+ $order_children = Order::query()->select(['id', 'order_num', 'store_id', 'money', 'time'])
+ ->where(['order_main_id' => $order_main_id])
+ ->get()
+ ->toArray();
+
+ foreach ($order_children as $key => &$item) {
+ // 商户/门店的openid
+ $store = Store::query()->select(['id', 'name', 'user_id'])
+ ->where(['id' => $item['store_id']])
+ ->first()->toArray();
+ $store['openid'] = Users::query()
+ ->where(['id' => $store['user_id']])
+ ->value('openid');
+
+ // 模板数据
+ $data_store = [
+ 'first' => '您有新订单收入!订单编号:'.$item['order_num'],
+ 'keyword' => [
+ $store['name']?:'',
+ $item['time']?:'',
+ '暂无',
+ $item['money']
+ ],
+ 'remark' => '感谢您的使用!'
+ ];
+
+ $ret_store = $this->sendTempMsg($store['openid'], 'lxVbC6PVpKbiO44bYqLmacl-BaME70D47Q0jn2Link0',$data_store);
+ }
+
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function sendTemMsgForAward($money, $note, $openid, $time)
+ {
+ // 模板数据发送消息给用户
+ $data_user = [
+ 'first' => '恭喜!您有一笔新的奖励收入!',
+ 'keyword' => [
+ $money,
+ $note,
+ $time
+ ],
+ 'remark' => '感谢您的使用!'
+ ];
+
+ // 获取用户openid,发送给用户
+ $ret_user = $this->sendTempMsg($openid,'ypZ7xdHUjWrRG8P-MD42dhpp6kUlh4Unoh7eTSrLZEg', $data_user);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function sendTempMsg($openid, $template_id, $data, $redirect_url = '', $applet_config = ['appid' => '', 'pagepath' => ''])
+ {
+ // 先拼个基础的
+ $template = [
+ 'touser' => $openid,
+ 'mp_template_msg' => [
+ 'appid' => env('OFFICIAL_APP_ID'),
+ 'template_id' => $template_id,
+ 'url' => $redirect_url,
+ ]
+ ];
+
+ // 看看有没有小程序跳转的要求
+ $template['mp_template_msg']['miniprogram'] = $applet_config;
+
+ // 重点来了,拼接关键数据data
+ if (!is_array($data)) { # 数组都不是,请回去
+ return false;
+ }
+
+ if (is_array($data['first'])) {
+ $template['mp_template_msg']['data']['first']['value'] = $data['first'][0] ?? '';
+ $template['mp_template_msg']['data']['first']['color'] = $data['first'][1] ?? '';
+ } else {
+ $template['mp_template_msg']['data']['first']['value'] = $data['first'];
+ }
+
+ if (isset($data['keyword'])&&is_array($data['keyword'])) {
+ foreach ($data['keyword'] as $key => &$keyword) {
+ $index = $key+1;
+
+ if (is_array($keyword)) {
+ $template['mp_template_msg']['data']['keyword'.$index]['value'] = $keyword[0] ?? '';
+ $template['mp_template_msg']['data']['keyword'.$index]['color'] = $keyword[1] ?? '';
+ } else {
+ $template['mp_template_msg']['data']['keyword'.$index]['value'] = $keyword;
+ }
+
+ }
+ }
+
+ if (is_array($data['remark'])) {
+ $template['mp_template_msg']['data']['remark']['value'] = $data['remark'][0] ?? '';
+ $template['mp_template_msg']['data']['remark']['color'] = $data['remark'][1] ?? '';
+ } else {
+ $template['mp_template_msg']['data']['remark']['value'] = $data['remark'];
+ }
+
+ $app = Factory::miniProgram(config('wxtempmsg'));
+ $app['guzzle_handler'] = CoroutineHandler::class;
+ $app->uniform_message->send($template);
+ }
+
+}
\ No newline at end of file
diff --git a/app/Service/MiniprogramServiceInterface.php b/app/Service/MiniprogramServiceInterface.php
new file mode 100644
index 0000000..1be9d80
--- /dev/null
+++ b/app/Service/MiniprogramServiceInterface.php
@@ -0,0 +1,41 @@
+ '', 'pagepath' => '']);
+}
\ No newline at end of file
diff --git a/app/Service/MqttServiceInterface.php b/app/Service/MqttServiceInterface.php
new file mode 100644
index 0000000..3148be2
--- /dev/null
+++ b/app/Service/MqttServiceInterface.php
@@ -0,0 +1,29 @@
+select(['id','order_num','money', 'pay_type', 'store_id', 'type']);
+ if ($isMain) {
+ $orders = $orders->where(['order_main_id' => $orderId])->get()->toArray();
+ } else {
+ $orders = $orders->where(['id' => $orderId])->get()->toArray();
+ }
+
+ if(empty($orders)) return;
+
+ // 循环发送
+ foreach ($orders as $k => &$order) {
+ $order['template'] = "懒族支付到账".floatval($order['money'])."元";
+ // 获取终端ID
+ $order['to_client_id'] = Store::query()->where(['id' => $order['store_id']])->value('loudspeaker_imei');
+ // 发布订阅消息
+ $res = $this->publish($order['template'], self::TOPIC, $order['to_client_id']);
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function publish(
+ $message,
+ $topic,
+ $toClientId = '',
+ $type = '',
+ $payId = '',
+ $curClientId = ''
+ ) {
+
+ $client = new MQTTClient(env('MQTT_HOST'), env('MQTT_PORT'));
+ $client->setAuthentication(env('MQTT_NAME'), env('MQTT_PASS'));
+ $client->setDebug(true);
+
+ if (env('MQTT_CERT')) {
+ $client->setEncryption(env('MQTT_CERT'));
+ }
+
+ $msgArr = [];
+ if ( (empty($type)&&is_numeric($message)) || 'cash' === $type ) {
+ $msgArr['cash'] = $message;
+ $payId AND $msgArr['payid'] = $payId;
+ } else {
+ $msgArr['message'] = $message;
+ }
+
+ if (!empty($toClientId)) {
+ $topic .= '/'.$toClientId;
+ }
+
+ $curClientId OR $curClientId = (string)rand(1,999999999);
+ $success = $client->sendConnect($curClientId);
+ if ($success) {
+ $client->sendPublish($topic, json_encode($msgArr), MQTTClient::MQTT_QOS2);
+ $client->sendDisconnect();
+ }
+ $client->close();
+ }
+}
\ No newline at end of file
diff --git a/app/Service/OrderService.php b/app/Service/OrderService.php
new file mode 100644
index 0000000..b637ac3
--- /dev/null
+++ b/app/Service/OrderService.php
@@ -0,0 +1,520 @@
+existsByOrderNum($data['order_num'])) {
+ return $orderMainId;
+ }
+
+ Db::beginTransaction();
+ try {
+
+ // 计算当前订单可用红包优惠金额
+ $couponMoney = 0;
+ $receiveCouponIds = [];
+ if (isset($data['receive_coupon_ids'])&&$data['receive_coupon_ids']) {
+ $receiveCouponIds = explode(',', str_replace(',',',',$data['receive_coupon_ids']));
+ $couponMoney = $this->getCouponAmount($receiveCouponIds, $data['money'], $data['user_id'], $data['market_id']);
+ }
+ $dataMain['yhq_money2'] = $couponMoney;
+
+ // 获取分布式全局ID
+ $generator = ApplicationContext::getContainer()->get(IdGeneratorInterface::class);
+ $dataMain['global_order_id'] = $generator->generate();
+
+ // 店铺IDs
+ $dataMain['store_ids'] = '';
+ $storeList = json_decode(html_entity_decode($data['store_list']), true);
+ if (!is_array($storeList)||empty($storeList)) {
+ Db::rollBack();
+ return '订单中商品不存在或已失效';
+ }
+
+ // 获取商户IDs
+ foreach ($storeList as &$item) {
+ $dataMain['store_ids'] .= empty($dataMain['store_ids']) ? $item['store_id'] : ','.$item['store_id'];
+ }
+
+ // 主订单插入数据
+ $currentTime = time();
+ $dataMain['time'] = date('Y-m-d H:i:s', $currentTime);
+ $dataMain['time_add'] = $currentTime;
+ $dataMain['pay_time'] = '';
+ $dataMain['state'] = OrderMain::ORDER_STATE_UNPAY;
+ $dataMain['code'] = $dataMain['global_order_id'];
+ $dataMain['jj_note'] = '';
+
+ // 主订单模型保存
+ $orderMain = OrderMain::create($dataMain);
+ $orderMainId = $orderMain->id;
+
+ // 统计订单中所有店铺当日订单数,做店铺订单序号
+ $countsArr = Order::query()
+ ->selectRaw('id, COUNT(*) AS count')
+ ->whereIn('store_id', explode(',', $dataMain['store_ids']))
+ ->where(['type' => OrderMain::ORDER_TYPE_ONLINE])
+ ->whereBetween('time', [date('Y-m-d 00:00:00'), date('Y-m-d 23:59:59')])
+ ->get()
+ ->toArray();
+
+ $storeOrderCounts = [];
+ foreach ($countsArr as $key => &$row) {
+ $storeOrderCounts[$row['id']] = $row['count'];
+ }
+
+ // 循环处理订单总额、子订单总额、商品、商户订单等信息
+ $orderAmountTotal = 0; # 总订单金额
+ $orderGoods = [];
+ foreach ($storeList as $key => &$item) {
+
+ // 子订单数据处理
+ $dataChild = [
+ 'uniacid' => $data['uniacid'],
+ 'order_num' => 's'.date('YmdHis', time()) . rand(1111, 9999),
+ 'user_id' => $orderMain->user_id,
+ 'store_id' => $item['store_id'],
+ 'order_main_id' => $orderMainId,
+ 'state' => OrderMain::ORDER_STATE_UNPAY,
+ 'tel' => $orderMain->tel,
+ 'name' => $orderMain->name,
+ 'address' => $orderMain->address,
+ 'area' => $orderMain->area,
+ 'time' => date("Y-m-d H:i:s"),
+ 'note' => $item['note'] ?? '',
+ 'delivery_time' => $orderMain->delivery_time,
+ 'type' => $orderMain->type,
+ 'lat' => $orderMain->lat,
+ 'lng' => $orderMain->lng,
+ 'pay_type' => $orderMain->pay_type,
+ 'order_type' => $orderMain->order_type,
+ 'money' => floatval($item['subtotal']),
+ 'box_money' => floatval($item['box_money']),
+ 'mj_money' => floatval($item['mj_money']),
+ 'yhq_money' => floatval($item['yhq_money']),
+ 'yhq_money2' => floatval($item['yhq_money2']),
+ 'zk_money' => floatval($item['zk_money']),
+ 'coupon_id' => $item['coupon_id'],
+ 'coupon_id2' => $item['coupon_id2'],
+ 'xyh_money' => floatval($item['xyh_money']),
+ 'oid' => (isset($storeOrderCounts[$item['store_id']]) ? $item['store_id'] : 0) + 1,
+ 'time_add' => date("Y-m-d H:i:s"),
+ 'jj_note' => '',
+ 'form_id' => '',
+ 'form_id2' => '',
+ 'code' => '',
+ ];
+
+ $orderChildId = Order::query()->insertGetId($dataChild);
+
+ // 子订单内商品处理
+ $goodsAmountTotal = 0;
+
+ if (!is_array($item['good_list'])||empty($item['good_list'])) {
+ Db::rollBack();
+ return '订单商品异常';
+ }
+ foreach ($item['good_list'] as &$goods) {
+ $goodsAmount = bcadd(floatval($goods['money']), floatval($goods['box_money']));
+ $goodsAmount = bcmul($goodsAmount, $goods['num']);
+ $goodsAmountTotal = bcadd($goodsAmountTotal, $goodsAmount);
+
+ $orderGoods[$goods['id']] = $goods;
+ $orderGoods[$goods['id']]['uniacid'] = $data['uniacid'];
+ $orderGoods[$goods['id']]['order_id'] = $orderChildId;
+ $orderGoods[$goods['id']]['user_id'] = $dataMain['user_id'];
+ $orderGoods[$goods['id']]['store_id'] = $item['store_id'];
+ }
+
+ // 子订单优惠总额
+ $discountAmountTotal = bcadd($dataChild['mj_money'], $dataChild['yhq_money']);
+ $discountAmountTotal = bcadd($discountAmountTotal, $dataChild['yhq_money2']);
+ $discountAmountTotal = bcadd($discountAmountTotal, $dataChild['zk_money']);
+ $discountAmountTotal = bcadd($discountAmountTotal, $dataChild['xyh_money']);
+
+ $goodsAmountTotal = bcsub($goodsAmountTotal, $discountAmountTotal, 2);
+ $orderAmountTotal = bcadd($orderAmountTotal, $goodsAmountTotal, 2);
+
+ // 校验子订单金额
+ if ($goodsAmountTotal != $dataChild['money']) {
+ Db::rollBack();
+ return '店铺订单总金额错误';
+ }
+
+ }
+
+ // 校验库存
+ foreach ($orderGoods as $Key => &$goodsItem) {
+
+ $goodsItem['combination_id'] = intval($goodsItem['combination_id']);
+
+ // 存在规格,则去规格处查库存
+ $goods = [];
+ if ($goodsItem['combination_id'] > 0) {
+
+ $combination = SpecCombination::query()
+ ->select(['good_id AS id', 'number AS inventory'])
+ ->where(['id' => $goodsItem['combination_id']])
+ ->first()
+ ->toArray();
+
+ $goods = Goods::query()
+ ->select(['id', 'name', 'is_max'])
+ ->where(['id' => $combination['id']])
+ ->first()
+ ->toArray();
+ $goods['inventory'] = $combination['inventory'];
+
+ } else {
+
+ $goods = Goods::query()
+ ->select(['id', 'name', 'is_max', 'inventory'])
+ ->where(['id' => $goodsItem['good_id']])
+ ->first()
+ ->toArray();
+
+ }
+
+ if (!$goods) {
+ Db::rollBack();
+ return '缺少商品';
+ }
+
+ if($goodsItem['num'] > $goods['inventory'] && $goods['is_max'] != Goods::INVENTORY_NOLIMIT){
+ Db::rollBack();
+ return '商品 '.$goods->name.' 库存不足!';
+ }
+
+ }
+
+ // 校验总订单金额
+ $deliveryAmount = 0; # 配送费用
+ if($dataMain['order_type'] == OrderMain::ORDER_TYPE_ONLINE){
+ $deliveryAmount = $dataMain['dada_fee'];
+ }
+
+ $orderAmountTotal = bcadd($orderAmountTotal, $deliveryAmount);
+ # 总订单优惠总额
+ $discountAmountTotal = bcadd($dataMain['mj_money'], $dataMain['yhq_money']);
+ $discountAmountTotal = bcadd($discountAmountTotal, $dataMain['yhq_money2']);
+ $discountAmountTotal = bcadd($discountAmountTotal, $dataMain['zk_money']);
+ $discountAmountTotal = bcadd($discountAmountTotal, $dataMain['xyh_money']);
+ $orderAmountTotal = bcsub($orderAmountTotal, $discountAmountTotal, 2);
+
+ if ($orderAmountTotal != bcsub(bcadd($dataMain['money'], $deliveryAmount), $discountAmountTotal, 2)) {
+ Db::rollBack();
+ return '订单总金额错误';
+ }
+
+ // 添加订单商品
+ $tempGoods = $orderGoods;
+ $orderGoods = [];
+ foreach ($tempGoods as $key => &$value) {
+ $goodsTemp['good_id'] = $value['good_id'];
+ $goodsTemp['img'] = $value['logo'];
+ $goodsTemp['number'] = $value['num'];
+ $goodsTemp['order_id'] = $value['order_id'];
+ $goodsTemp['name'] = $value['name'];
+ $goodsTemp['money'] = $value['money'];
+ $goodsTemp['dishes_id'] = $value['dishes_id'];
+ $goodsTemp['spec'] = $value['spec'];
+ $goodsTemp['is_qg'] = $value['is_qg'];
+ $goodsTemp['good_unit'] = $value['good_unit'];
+ $goodsTemp['uniacid'] = $value['uniacid'];
+ $goodsTemp['combination_id'] = $value['combination_id'];
+ $orderGoods[] = $goodsTemp;
+ }
+
+ $addOrderGoods = OrderGoods::query()->insert($orderGoods);
+ if (!$addOrderGoods) {
+ Db::rollBack();
+ return '订单商品异常';
+ }
+
+ // 修改总订单金额,金额是计算来的
+ // TODO 这部分其实可以结合处理优化一下,循环前后关联处理太多
+ $updateOrderMain = OrderMain::query()->where(['id' => $orderMainId])->update(['money' => $orderAmountTotal, 'total_money' => $dataMain['money']]);
+ if (!$updateOrderMain) {
+ Db::rollBack();
+ return '订单总金额记录失败';
+ }
+
+ // 处理红包的使用
+ $canUseCoupons = CouponUserRec::select(['id', 'user_id', 'number', 'number_remain', 'system_coupon_user_id'])
+ ->whereIn('id', $receiveCouponIds)
+ ->get()->toArray();
+ if (is_array($canUseCoupons)&&!empty($canUseCoupons)) {
+ # 使用记录、更新当前优惠券
+ foreach ($canUseCoupons as $key => &$coupon) {
+
+ $couponUse = [
+ 'user_id' => $coupon['user_id'],
+ 'user_receive_id' => $coupon['id'],
+ 'system_coupon_id' => $coupon['system_coupon_user_id'],
+ 'order_main_id' => $orderMainId,
+ 'use_time' => $currentTime,
+ 'return_time' => 0,
+ 'number' => 1,
+ 'status' => 1,
+ 'update_time' => 0,
+ ];
+ var_dump('$couponUse',$couponUse);
+
+ $insertRes = CouponUserUse::query()->insert($couponUse);
+
+ if ($insertRes) {
+ $numberRemain = $coupon['number_remain'] - 1;
+ if ($numberRemain == 0) {
+ $status = 2;
+ } elseif ($numberRemain > 0 && $numberRemain < $coupon['number']) {
+ $status = 1;
+ } elseif ($numberRemain == $coupon['number']) {
+ $status = 0;
+ }
+
+ $upRes = CouponUserRec::query()->where(['id' => $coupon['id']])->update(['number_remain' => $numberRemain, 'status' => $status]);
+
+ if (!$upRes) {
+ Db::rollBack();
+ return '优惠券使用失败';
+ }
+
+ // 缓存使用记录
+ $usedRes = $this->couponService->cacheTodayCouponUsed($coupon['user_id'], $coupon['system_coupon_user_id'], $coupon['id']);
+ if (!$usedRes) {
+ Db::rollBack();
+ return '优惠券使用失败';
+ }
+
+ } else {
+ Db::rollBack();
+ return '优惠券使用失败';
+ }
+
+ }
+ }
+
+ Db::commit();
+ return $orderMainId;
+
+ } catch (Exception $e) {
+
+ Db::rollBack();
+ return $e->getMessage();
+
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function addOfflineOrder($data)
+ {
+ Db::beginTransaction();
+ try {
+ // 主订单数据
+ $dataMain = [];
+
+ // 获取分布式全局ID
+ $generator = ApplicationContext::getContainer()->get(IdGeneratorInterface::class);
+ $globalRrderId = $generator->generate();
+
+ // 主订单插入数据
+ $currentTime = time();
+ $dataMain = [
+ 'delivery_no' => '',
+ 'dada_fee' => 0,
+ 'market_id' => 0,
+ 'box_money' => 0,
+ 'ps_money' => 0,
+ 'mj_money' => 0,
+ 'xyh_money' => 0,
+ 'yhq_money' => 0,
+ 'yhq_money2' => 0,
+ 'zk_money' => 0,
+ 'tel' => '',
+ 'name' => '',
+ 'address' => '',
+ 'area' => '',
+ 'lat' => '',
+ 'lng' => '',
+ 'note' => '',
+ 'form_id' => '',
+ 'form_id2' => '',
+ 'delivery_time' => '',
+ 'order_type' => 0,
+ 'coupon_id' => 0,
+ 'coupon_id2' => 0,
+ 'store_list' => '',
+ 'receive_coupon_ids' => '',
+ 'type' => OrderMain::ORDER_TYPE_OFFLINE,
+ 'time' => date('Y-m-d H:i:s', $currentTime),
+ 'time_add' => $currentTime,
+ 'pay_time' => '',
+ 'pay_type' => OrderMain::ORDER_PAY_WX,
+ 'state' => OrderMain::ORDER_STATE_UNPAY,
+ 'dm_state' => OrderMain::ORDER_STATE_UNPAY,
+ 'code' => $globalRrderId,
+ 'jj_note' => '',
+ 'uniacid' => 2,
+ 'order_num' => 'dm'.date('YmdHis', time()) . rand(1111, 9999),
+ 'money' => $data['money'],
+ 'user_id' => $data['user_id'],
+ 'store_ids' => $data['store_id'],
+ 'global_order_id' => $globalRrderId,
+ ];
+
+ // 主订单模型保存
+ $orderMain = OrderMain::create($dataMain);
+ $orderMainId = $orderMain->id;
+
+ // 子订单模型保存
+ $dataChild = [
+ 'uniacid' => 1,
+ 'order_num' => 's'.date('YmdHis', time()) . rand(1111, 9999),
+ 'user_id' => $orderMain->user_id,
+ 'store_id' => $data['store_id'],
+ 'order_main_id' => $orderMainId,
+ 'state' => OrderMain::ORDER_STATE_UNPAY,
+ 'dm_state' => OrderMain::ORDER_STATE_UNPAY,
+ 'tel' => $orderMain->tel,
+ 'name' => $orderMain->name,
+ 'address' => $orderMain->address,
+ 'area' => $orderMain->area,
+ 'time' => date("Y-m-d H:i:s"),
+ 'note' => '',
+ 'delivery_time' => $orderMain->delivery_time,
+ 'type' => $orderMain->type,
+ 'lat' => $orderMain->lat,
+ 'lng' => $orderMain->lng,
+ 'pay_type' => $orderMain->pay_type,
+ 'order_type' => $orderMain->order_type,
+ 'money' => $data['money'],
+ 'box_money' => 0,
+ 'mj_money' => 0,
+ 'yhq_money' => 0,
+ 'yhq_money2' => 0,
+ 'zk_money' => 0,
+ 'coupon_id' => 0,
+ 'coupon_id2' => 0,
+ 'xyh_money' => 0,
+ 'time_add' => date("Y-m-d H:i:s"),
+ 'jj_note' => '',
+ 'form_id' => '',
+ 'form_id2' => '',
+ 'code' => '',
+ ];
+
+ $orderChildId = Order::query()->insertGetId($dataChild);
+
+ Db::commit();
+ return $orderMainId;
+ } catch (Exception $e) {
+ Db::rollBack();
+ return '购买失败';
+ }
+ }
+
+
+
+ /**
+ * 计算和校验当前订单可用红包及金额
+ * @param $couponIds
+ * @param $orderAmount
+ * @param $userId
+ * @param $marketId
+ * @throws Exception
+ */
+ protected function getCouponAmount($couponIds, $orderAmount, $userId, $marketId)
+ {
+
+ // 用户当前订单可用优惠券
+ $couponsCanUse = $this->couponService->getOrderCanUseCoupons(
+ $orderAmount,
+ $marketId,
+ $userId,
+ [
+ 'receive.id',
+ 'receive.user_id',
+ 'receive.number',
+ 'receive.number_remain',
+ 'receive.system_coupon_user_id',
+ 'coupon.discounts',
+ 'coupon.discount_type',
+ ]
+ );
+
+ $couponCanUseIds = array_column($couponsCanUse, 'id');
+ $couponCanUseIds = array_intersect($couponCanUseIds, $couponIds);
+ $couponCannotUseIds = array_diff($couponIds, $couponCanUseIds);
+
+ if (empty($couponCanUseIds)||!empty($couponCannotUseIds)) {
+ throw new Exception('您的订单中有优惠券已经失效');
+ }
+
+ // 计算红包折扣金额
+ $couponMoney = 0;
+ foreach ($couponsCanUse as $key => $coupon) {
+
+ if (!in_array($coupon->id, $couponIds)) {
+ continue;
+ }
+
+ if ($coupon->discount_type == Coupon::DISCOUNT_TYPE_CASH) {
+ $couponMoney = bcadd($couponMoney, $coupon->discounts, 2);
+ } elseif ($coupon->discount_type == Coupon::DISCOUNT_TYPE_RATE) {
+ $discountRate = bcdiv($coupon->discounts,10);
+ $discountRate = bcsub(1,$discountRate);
+ $discountMoney = bcmul($orderAmount, $discountRate);
+ $couponMoney = bcadd($couponMoney, $discountMoney, 2);
+ }
+ }
+
+ return $couponMoney;
+
+ }
+
+ /**
+ * 订单是否存在
+ * @param $orderNum
+ * @return \Hyperf\Utils\HigherOrderTapProxy|mixed|void|null
+ */
+ public function existsByOrderNum($orderNum)
+ {
+ return OrderMain::query()->where(['order_num' => $orderNum])->value('id');
+ }
+}
\ No newline at end of file
diff --git a/app/Service/OrderServiceInterface.php b/app/Service/OrderServiceInterface.php
new file mode 100644
index 0000000..0d5e0a2
--- /dev/null
+++ b/app/Service/OrderServiceInterface.php
@@ -0,0 +1,29 @@
+where(['user_id' => $user_id])
+ ->where('id', '!=', $order_main_id)
->where(function ($query){
- $query->where('state', 'in', [4,5,10])
- ->orWhere('dm_state', 'in', [2,3]);
+ $query->whereIn('state', [OrderMain::ORDER_STATE_COMPLETE,OrderMain::ORDER_STATE_EVALUATED,OrderMain::ORDER_STATE_UNREFUND])
+ ->orWhereIn('dm_state', [OrderMain::ORDER_STATE_UNTAKE,OrderMain::ORDER_STATE_DELIVERY]);
})
->exists();
@@ -59,5 +62,18 @@ class UserService implements UserServiceInterface
return $result;
}
-
+ /**
+ * @inheritDoc
+ */
+ public function isStoreFirstOrderToday($user_id, $store_id, $current_order_id, $limit_amount = 3)
+ {
+ return !Order::query()
+ ->where(['user_id' => $user_id, 'store_id' => $store_id])
+ ->whereIn('dm_state', [OrderMain::ORDER_STATE_UNTAKE,OrderMain::ORDER_STATE_DELIVERY])
+ ->where('time_add', '>=', date('Y-m-d 00:00:00'))
+ ->where('time_add', '<=', date('Y-m-d 23:59:59'))
+ ->where('money', '>=', $limit_amount)
+ ->where('id', '!=', $current_order_id)
+ ->exists();
+ }
}
\ No newline at end of file
diff --git a/app/Service/UserServiceInterface.php b/app/Service/UserServiceInterface.php
index 06c1ea0..2920d53 100644
--- a/app/Service/UserServiceInterface.php
+++ b/app/Service/UserServiceInterface.php
@@ -9,10 +9,21 @@ interface UserServiceInterface
/**
* 是否平台新用户
* @param $user_id
+ * @param $order_main_id
* @return mixed
*/
- public function isStageNewUser($user_id): bool;
+ public function isStageNewUser($user_id, $order_main_id): bool;
public function saveUserUnionid($openid,$unionid);
+ /**
+ * 是否店铺当日首单
+ * @param $user_id
+ * @param $store_id
+ * @param $current_order_id
+ * @param int $limit_amount
+ * @return mixed
+ */
+ public function isStoreFirstOrderToday($user_id, $store_id, $current_order_id, $limit_amount = 3);
+
}
\ No newline at end of file
diff --git a/app/TaskWorker/AliIotTask.php b/app/TaskWorker/AliIotTask.php
new file mode 100644
index 0000000..4b3b7b1
--- /dev/null
+++ b/app/TaskWorker/AliIotTask.php
@@ -0,0 +1,62 @@
+regionId('cn-shanghai')
+ ->asDefaultClient();
+
+ try {
+ AlibabaCloud::rpc()
+ ->product('Iot')
+ ->version('2018-01-20')
+ ->action('Pub')
+ ->method('POST')
+ ->host('iot.cn-shanghai.aliyuncs.com')
+ ->options([
+ 'query' => [
+ 'RegionId' => "cn-shanghai",
+ 'TopicFullName' => "/a1ZSurIJmO0/".$device_name."/user/get",
+ 'MessageContent' => base64_encode($msg),
+ 'ProductKey' => "a1ZSurIJmO0",
+ ],
+ ])
+ ->request();
+ } catch (ClientException $e) {
+ echo $e->getErrorMessage() . PHP_EOL;
+ } catch (ServerException $e) {
+ echo $e->getErrorMessage() . PHP_EOL;
+ }
+
+ return true;
+ }
+
+}
+
diff --git a/composer.json b/composer.json
index 6c643dd..284593c 100644
--- a/composer.json
+++ b/composer.json
@@ -33,7 +33,10 @@
"hyperf/task": "^2.0",
"hyperf/paginator": "^2.0",
"hyperf/amqp": "^2.0",
- "alibabacloud/iot": "^1.8"
+ "alibabacloud/iot": "^1.8",
+ "hyperf/snowflake": "^2.0",
+ "ext-bcmath": "*",
+ "overtrue/wechat": "~4.0"
},
"require-dev": {
"swoole/ide-helper": "^4.5",
diff --git a/config/autoload/dependencies.php b/config/autoload/dependencies.php
index 28a2970..e7ca333 100644
--- a/config/autoload/dependencies.php
+++ b/config/autoload/dependencies.php
@@ -9,6 +9,7 @@ declare(strict_types=1);
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
+
return [
\App\Service\ServiceEvaluateServiceInterface::class => \App\Service\ServiceEvaluateService::class,
\App\Service\AttachmentServiceInterface::class => \App\Service\AttachmentService::class,
@@ -18,4 +19,10 @@ return [
\App\Service\CouponRebateServiceInterface::class => \App\Service\CouponRebateService::class,
\App\Service\UserServiceInterface::class => \App\Service\UserService::class,
\App\Service\CouponServiceInterface::class => \App\Service\CouponService::class,
+ \App\Service\DeviceServiceInterface::class =>\App\Service\DeviceServiceImp::class,
+ \App\Service\IOTServiceInterface::class => \App\Service\IOTAliService::class,
+ \App\Service\OrderServiceInterface::class => \App\Service\OrderService::class,
+ \App\Service\MqttServiceInterface::class => \App\Service\MqttSpeakerService::class,
+ \App\Service\FeiePrintServiceInterface::class => \App\Service\FeiePrintService::class,
+ \App\Service\MiniprogramServiceInterface::class => \App\Service\MiniprogramService::class,
];
diff --git a/config/autoload/exceptions.php b/config/autoload/exceptions.php
index 0aa0812..8d97cf6 100644
--- a/config/autoload/exceptions.php
+++ b/config/autoload/exceptions.php
@@ -16,6 +16,7 @@ return [
App\Exception\Handler\AppExceptionHandler::class,
App\Exception\Handler\ValidationExceptionHandler::class,
App\Exception\Handler\SsdbExceptionHandler::class,
+ \App\Exception\Handler\FilesystemExceptionHandler::class,
],
],
];
diff --git a/config/autoload/file.php b/config/autoload/file.php
new file mode 100644
index 0000000..6744d66
--- /dev/null
+++ b/config/autoload/file.php
@@ -0,0 +1,94 @@
+ 'oss',
+ 'storage' => [
+ 'local' => [
+ 'driver' => \Hyperf\Filesystem\Adapter\LocalAdapterFactory::class,
+ 'root' => BASE_PATH . '/attachment',
+ ],
+ 'ftp' => [
+ 'driver' => \Hyperf\Filesystem\Adapter\FtpAdapterFactory::class,
+ 'host' => 'ftp.example.com',
+ 'username' => 'username',
+ 'password' => 'password',
+ // 'port' => 21,
+ // 'root' => '/path/to/root',
+ // 'passive' => true,
+ // 'ssl' => true,
+ // 'timeout' => 30,
+ // 'ignorePassiveAddress' => false,
+ ],
+ 'memory' => [
+ 'driver' => \Hyperf\Filesystem\Adapter\MemoryAdapterFactory::class,
+ ],
+ 's3' => [
+ 'driver' => \Hyperf\Filesystem\Adapter\S3AdapterFactory::class,
+ 'credentials' => [
+ 'key' => env('S3_KEY'),
+ 'secret' => env('S3_SECRET'),
+ ],
+ 'region' => env('S3_REGION'),
+ 'version' => 'latest',
+ 'bucket_endpoint' => false,
+ 'use_path_style_endpoint' => false,
+ 'endpoint' => env('S3_ENDPOINT'),
+ 'bucket_name' => env('S3_BUCKET'),
+ ],
+ 'minio' => [
+ 'driver' => \Hyperf\Filesystem\Adapter\S3AdapterFactory::class,
+ 'credentials' => [
+ 'key' => env('S3_KEY'),
+ 'secret' => env('S3_SECRET'),
+ ],
+ 'region' => env('S3_REGION'),
+ 'version' => 'latest',
+ 'bucket_endpoint' => false,
+ 'use_path_style_endpoint' => true,
+ 'endpoint' => env('S3_ENDPOINT'),
+ 'bucket_name' => env('S3_BUCKET'),
+ ],
+ 'oss' => [
+ 'driver' => \Hyperf\Filesystem\Adapter\AliyunOssAdapterFactory::class,
+ 'accessId' => env('OSS_ACCESS_ID'),
+ 'accessSecret' => env('OSS_ACCESS_SECRET'),
+ 'bucket' => env('OSS_BUCKET'),
+ 'endpoint' => env('OSS_ENDPOINT'),
+ // 'timeout' => 3600,
+ // 'connectTimeout' => 10,
+ // 'isCName' => false,
+ // 'token' => '',
+ ],
+ 'qiniu' => [
+ 'driver' => \Hyperf\Filesystem\Adapter\QiniuAdapterFactory::class,
+ 'accessKey' => env('QINIU_ACCESS_KEY'),
+ 'secretKey' => env('QINIU_SECRET_KEY'),
+ 'bucket' => env('QINIU_BUCKET'),
+ 'domain' => env('QINBIU_DOMAIN'),
+ ],
+ 'cos' => [
+ 'driver' => \Hyperf\Filesystem\Adapter\CosAdapterFactory::class,
+ 'region' => env('COS_REGION'),
+ 'credentials' => [
+ 'appId' => env('COS_APPID'),
+ 'secretId' => env('COS_SECRET_ID'),
+ 'secretKey' => env('COS_SECRET_KEY'),
+ ],
+ 'bucket' => env('COS_BUCKET'),
+ 'read_from_cdn' => false,
+ // 'timeout' => 60,
+ // 'connect_timeout' => 60,
+ // 'cdn' => '',
+ // 'scheme' => 'https',
+ ],
+ ],
+];
diff --git a/config/autoload/snowflake.php b/config/autoload/snowflake.php
new file mode 100644
index 0000000..279de42
--- /dev/null
+++ b/config/autoload/snowflake.php
@@ -0,0 +1,24 @@
+ MetaGeneratorInterface::DEFAULT_BEGIN_SECOND,
+ RedisMilliSecondMetaGenerator::class => [
+ 'pool' => 'default',
+ ],
+ RedisSecondMetaGenerator::class => [
+ 'pool' => 'default',
+ ],
+];
diff --git a/config/config.php b/config/config.php
index 7d0c07e..ba75ad6 100644
--- a/config/config.php
+++ b/config/config.php
@@ -28,4 +28,17 @@ return [
LogLevel::WARNING,
],
],
+ 'site_host'=> env('SITE_HOST', ''),
+ 'wxpay' => [
+ 'app_id' => env('APP_ID',''),
+ 'mch_id' => env('MCH_ID',''),
+ 'key' => env('MCH_KEY',''),
+ 'cert_path' => env('CERT_PATH',''),
+ 'key_path' => env('KEY_PATH',''),
+ 'notify_url' => env('NOTIFY_URL',''),
+ ],
+ 'wxtempmsg' => [
+ 'app_id' => env('APP_ID',''),
+ 'secret' => env('APP_SECRET',''),
+ ]
];
diff --git a/config/routes.php b/config/routes.php
index e68c605..b68b0e4 100644
--- a/config/routes.php
+++ b/config/routes.php
@@ -35,15 +35,28 @@ Router::addGroup('/v1/',function (){
Router::post('CouponRebate/tieCouponActive', 'App\Controller\CouponRebateController@tieCouponActive');
Router::post('CouponRebate/couponRebate', 'App\Controller\CouponRebateController@couponRebate');
Router::post('CouponRebate/clearSsdbReceive', 'App\Controller\CouponRebateController@clearSsdbCouponReceiveByName');
- //
+ //播报器相关
Router::post('Device/bind', 'App\Controller\DeviceController@bind');
Router::post('Device/list', 'App\Controller\DeviceController@list');
Router::post('Device/unbind', 'App\Controller\DeviceController@unbind');
- //·
+ //测试路由
Router::get('test/index1', 'App\Controller\TestController@index1');
Router::post('Store/applyEntry', 'App\Controller\StoreController@applyEntry');
Router::post('Community/bind', 'App\Controller\CommunityController@bind');
Router::post('user/saveUserUnionid', 'App\Controller\UserController@saveUserUnionid');
+
+ //订单相关
+ Router::post('Order/addOnline', 'App\Controller\OrderController@addOnlineOrder');
+ Router::post('Order/addOffline', 'App\Controller\OrderController@addOfflineOrder');
+
+ //小程序支付相关
+ Router::post('wxminipay/online', 'App\Controller\PaymentController@wxminiPayOnline');
+ Router::post('wxminipay/offline', 'App\Controller\PaymentController@wxminiPayOffline');
+},['middleware' => [\App\Middleware\Auth\ApiMiddleware::class]]);
+
+Router::addGroup('/wechat/',function () {
+ Router::post('notify/wxminionline', 'App\Controller\NotifyController@wxminiOnline');
+ Router::post('notify/wxminioffline', 'App\Controller\NotifyController@wxminiOffline');
});
\ No newline at end of file