diff --git a/.gitignore b/.gitignore
index 9091e68..9454e2b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,3 +12,4 @@ vendor/
*.lock
.phpunit*
/watch
+.vscode/
diff --git a/Envoy.blade.php b/Envoy.blade.php
index 6562fe6..39d6cc9 100644
--- a/Envoy.blade.php
+++ b/Envoy.blade.php
@@ -21,7 +21,7 @@
cd /lanzu_api
git pull origin master
@if($composer == true)
- composer update --lock
+ composer update --no-dev --lock
@endif
supervisorctl restart lanzu_api
@endtask
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/Amqp/Consumer/couponRebateConsumer.php b/app/Amqp/Consumer/couponRebateConsumer.php
new file mode 100644
index 0000000..a76f046
--- /dev/null
+++ b/app/Amqp/Consumer/couponRebateConsumer.php
@@ -0,0 +1,42 @@
+CouponRebateService->couponRebate($data);
+ if (!$res) {
+ return Result::REQUEUE;
+ }
+ return Result::ACK;
+ }
+
+ public function isEnable(): bool
+ {
+ return parent::isEnable();
+ }
+}
diff --git a/app/Commons/Log.php b/app/Commons/Log.php
new file mode 100644
index 0000000..29a4e84
--- /dev/null
+++ b/app/Commons/Log.php
@@ -0,0 +1,88 @@
+clientFactory = $clientFactory;
+ }
+
+ public function getClient()
+ {
+ // $options 等同于 GuzzleHttp\Client 构造函数的 $config 参数
+ $options = [
+ 'timeout' => 2.0,
+ ];
+ // $client 为协程化的 GuzzleHttp\Client 对象
+ $client = $this->clientFactory->create($options);
+
+ return $client;
+ }
+
+ public function event($labels=null,$datas){
+
+ co(function () use ($labels,$datas){
+
+ $client = $this->getClient();
+ $kv = [];
+ foreach ($datas as $key => $value) {
+ $kv[] = $key."=".$value;
+ }
+ $pushLabels = [];
+
+ $event_name = 'event_'.env('APP_ENV');
+ if(!empty($labels)) $pushLabels[$event_name] = $labels;
+
+ /*
+ * data format:
+ curl -v -H "Content-Type: application/json" -XPOST -s "http://39.96.12.39:3100/loki/api/v1/push" --data-raw \
+ '{"streams": [{ "stream": { "foo": "bar2" }, "values": [ [ "1596274538882028800", "fizzbuzz" ] ] }]}'
+ */
+ $ts = $this->getMsecTime() . '000000';
+ $datas = implode("&",$kv);
+ $values = [[$ts,$datas]];
+ $app_name = env('APP_NAME').'_'.env('APP_ENV');
+
+ $pushLabels['app']= $app_name;
+ $pushDatas = [
+ 'streams'=>[
+ [
+ 'stream'=>$pushLabels,
+ 'values'=>$values,
+ ]
+ ]
+ ];
+ $client->post(
+ env('LOG_HOST','http://39.96.12.39:3100').'/loki/api/v1/push',
+ [
+ 'headers'=>[
+ 'Content-Type'=>'application/json'
+ ],
+ 'body' => json_encode($pushDatas)
+ ]
+ );
+ //var_dump(json_encode($pushDatas) );
+ });
+ }
+
+
+ public function push($datas){
+ $this->event(null,$datas);
+ }
+
+ public function getMsecTime()
+ {
+ list($msec, $sec) = explode(' ', microtime());
+ $msectime = (float)sprintf('%.0f', (floatval($msec) + floatval($sec)) * 1000);
+ return $msectime;
+ }
+
+}
diff --git a/app/Constants/ErrorCode.php b/app/Constants/ErrorCode.php
index a7fee16..ca97510 100644
--- a/app/Constants/ErrorCode.php
+++ b/app/Constants/ErrorCode.php
@@ -33,4 +33,25 @@ class ErrorCode extends AbstractConstants
* @Message("Save failure!");
*/
const SAVE_FAILURE = 100;
+
+ /**
+ * @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
new file mode 100644
index 0000000..edcb57c
--- /dev/null
+++ b/app/Constants/LogLabel.php
@@ -0,0 +1,36 @@
+success($this->adService->banners());
+ }
+}
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/CouponController.php b/app/Controller/CouponController.php
index 6e73066..dc8ae7c 100644
--- a/app/Controller/CouponController.php
+++ b/app/Controller/CouponController.php
@@ -12,85 +12,33 @@ declare(strict_types=1);
namespace App\Controller;
+use Hyperf\Di\Annotation\Inject;
use App\Model\CouponUserRecType;
use App\Model\Coupon;
use App\Model\CouponRec;
use Hyperf\DbConnection\Db;
use Hyperf\Redis\Redis;
use Hyperf\Utils\ApplicationContext;
+use App\Request\CouponGetListRequest;
+use App\Service\CouponServiceInterface;
class CouponController extends BaseController
{
+ /**
+ * @Inject
+ * @var CouponServiceInterface
+ */
+ protected $couponService;
/**
* 获取用户可领取优惠卷接口
*/
- public function getSystemCouponUserList()
+ public function getSystemCouponUserList(CouponGetListRequest $validator)
{
- $user_id = $this->request->input('user_id', 0);
- $receive_type = $this->request->input('receive_type', 0);
-
- if ($this->empty($user_id) || $this->empty($receive_type)) {
- return $this->success(['not_reveive' => []]);
- }
-
- $c_ids = [];
- // 渠道开启,查询该渠道可以领取的优惠券ID
- // 渠道未开启,查询所有优惠券
- if (env('SUB_CHANNEL') == 1) {
- $c_ids = CouponUserRecType::where('receive_type', $receive_type)->pluck('system_coupon_user_id');
- } else {
- $c_ids = Coupon::pluck('id');
- }
-
- $nowTime = time();
-
- $where = [
- ['user_id',"=",$user_id]
- ];
-
- // 渠道开启,查询该用户在此渠道领过的优惠券ID
- if (env('SUB_CHANNEL') == 1) {
- array_push($where, ['receive_type', "=", $receive_type]);
- }
-
- $cr_ids = CouponRec::where($where)->pluck('system_coupon_user_id');
-
- //领过券的ID
- $c_ids = $c_ids->toArray();
- $cr_ids = $cr_ids->toArray();
-
- // 当前用户可领的优惠券ID
- $couponIds = array_diff($c_ids, $cr_ids);
-
- $whereC = [
- ['u.end_time','>',$nowTime],
- ['u.start_time','<=',$nowTime],
- ['u.status','=',1],
- ];
-
- if (env('SUB_CHANNEL') == 1) {
- array_push($whereC, ['type.receive_type','=',$receive_type]);
- }
-
- $c = Db::table('ims_system_coupon_user as u')
- ->where($whereC)
- ->join('ims_system_coupon_user_receivetype as type', 'u.id', '=', 'type.system_coupon_user_id')
- ->whereRaw('u.inventory_use < u.inventory and u.inventory-u.inventory_use >= type.one_receive_number')
- // ->whereIn('u.id',$c_ids)
- // ->whereNotIn('u.id',$cr_ids)
- ->whereIn('u.id', $couponIds)
- ->select('u.*','type.one_receive_number')
- ->orderBy('u.weigh','desc')
- // ->orderByRaw('FIELD(u.id, '.implode(", " , $ids).')')
- ->limit(4)
- ->get();
- foreach ($c as $k => &$v){
- if($v->discount_type == 2){
- $v->discounts = floatval($v->discounts);
- }
- }
- return $this->success(['not_reveive'=>$c]);
+ $userId = $this->request->input('user_id', 0);
+ $receiveType = $this->request->input('receive_type', 0);
+ $res = $this->couponService->getSystemCouponUserList($userId,$receiveType);
+ return $this->success($res);
}
//统计用户
@@ -205,11 +153,23 @@ class CouponController extends BaseController
['receive.user_id','=',$userId],
])
->whereIn('receive.status',[0,1])
- ->select('u.*','receive.number_remain')
+ ->select('u.title','u.discounts','u.full_amount','u.discount_type','u.introduce','u.usable_end_time','receive.number_remain')
->orderBy('u.weigh','desc')
->get();
foreach ($coupons as $key => $coupon) {
+ //拼接满减文字提示
+ $coupon->full_amount_text = '满' . $coupon->full_amount . "可用";
+ //判断是折扣优惠券还是满减优惠券
+ if($coupon->discount_type == 1){
+ $coupon->discounts_text = '¥'.$coupon->discounts;
+ }elseif($coupon->discount_type == 2){
+ $coupon->discounts_text = floatval($coupon->discounts)."折";
+ }
+ //失效时间格式转换
+ $usable_end_time = date('Y-m-d H:i:s',$coupon->usable_end_time);
+ $coupon->usable_end_time_text = '有效期至:'.$usable_end_time;
+
if ($coupon->usable_end_time < $nowTime || $coupon->number_remain <= 0) {
$expired[] = $coupon;
} else {
diff --git a/app/Controller/CouponRebateController.php b/app/Controller/CouponRebateController.php
new file mode 100644
index 0000000..c1ea42a
--- /dev/null
+++ b/app/Controller/CouponRebateController.php
@@ -0,0 +1,87 @@
+request->input('user_id', 0);
+ $res = $this->CouponRebateService->isCouponRebate($user_id);
+ return $this->success($res);
+ }
+
+ /**
+ * 返回活动信息
+ */
+ public function getActiveInfo()
+ {
+ $res = $this->CouponRebateService->getActiveInfo();
+ return $this->success($res);
+ }
+
+
+ /**
+ * 用户领取优惠券
+ */
+ public function userReceiveCoupon(CouponRebateReceiveRequest $validator)
+ {
+ return $this->success($this->CouponRebateService->userReceiveCoupon($this->request->all()));
+ }
+
+ public function couponRebate()
+ {
+ $order_id = $this->request->input('order_id', 0);
+ $res = $this->CouponRebateService->couponRebate($order_id);
+ return $this->success($res);
+ }
+
+ /**
+ * 将优惠券绑定活动
+ */
+ public function tieCouponActive(CouponRebateTieRequest $validator)
+ {
+ $couponForward = $this->request->input('coupon_forward_ids',[]);
+ $couponForward = is_array($couponForward) ? implode(',',$couponForward) : $couponForward ;
+ $couponRepay = $this->request->input('coupon_repay_id',0);
+ $couponActivity = $this->request->input('coupon_activity',0);
+ $res = $this->CouponRebateService->tieCouponActive($couponActivity,$couponForward,$couponRepay);
+ return $this->success($res);
+ }
+
+ /**
+ * 清优惠券领取记录(SSDB)
+ */
+ public function clearSsdbCouponReceiveByName(){
+ $activity = $this->request->input('activity_type',0);
+ $userId = $this->request->input('user_id',0);
+ $get = $this->request->input('get',0);
+ $isAll = $this->request->input('is_all',0);;
+ return $this->success($this->CouponRebateService->clearSsdbCouponReceiveByName($activity,$userId, $get, $isAll));
+ }
+}
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..955db3f
--- /dev/null
+++ b/app/Controller/NotifyController.php
@@ -0,0 +1,413 @@
+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);
+ }
+
+ // 优惠券返券
+ $this->couponRebateService->couponRebateInTask($orderMain->id);
+
+ // 喇叭通知,兼容旧音响,MQTT+IOT
+ $res = $this->mqttSpeakerService->speakToStore($orderMain->id);
+ $this->log->event(
+ LogLabel::PAY_NOTIFY_WXMINI,
+ ['fail_mqtt' => json_encode($res)]
+ );
+ $res = $this->deviceService->pubMsgToStoreByOrderMainId($orderMain->id);
+ $this->log->event(
+ LogLabel::PAY_NOTIFY_WXMINI,
+ ['fail_device' => json_encode($res)]
+ );
+ // 公众号模板消息
+ $res = $this->miniprogramService->sendTemMsgForOnlineOrder($orderMain->id);
+ $this->log->event(
+ LogLabel::PAY_NOTIFY_WXMINI,
+ ['fail_mini' => json_encode($res)]
+ );
+ // 打印订单,自动打印 TODO 后续优化调用逻辑
+ $res = $this->feiePrintService->feiePrint($orderMain->global_order_id);
+ $this->log->event(
+ LogLabel::PAY_NOTIFY_WXMINI,
+ ['fail_feie' => json_encode($res)]
+ );
+ 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);
+ $this->log->event(
+ LogLabel::PAY_NOTIFY_WXMINI,
+ ['fail_mqtt' => json_encode($res)]
+ );
+ $res = $this->deviceService->pubMsgToStoreByOrderMainId($orderMain->id);
+ $this->log->event(
+ LogLabel::PAY_NOTIFY_WXMINI,
+ ['fail_device' => json_encode($res)]
+ );
+ // 公众号模板消息
+ $res = $this->miniprogramService->sendTemMsgForOfflineOrder($orderMain->id);
+ $this->log->event(
+ LogLabel::PAY_NOTIFY_WXMINI,
+ ['fail_mini' => json_encode($res)]
+ );
+ 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/ParamsTokenController.php b/app/Controller/ParamsTokenController.php
new file mode 100644
index 0000000..db1cdca
--- /dev/null
+++ b/app/Controller/ParamsTokenController.php
@@ -0,0 +1,40 @@
+paramsTokenService->generate($this->request->all());
+ return $this->success(['token' => $token]);
+ }
+
+ /**
+ * 解析token获取对应参数数据
+ * @return \Psr\Http\Message\ResponseInterface
+ */
+ public function analyze()
+ {
+ $params = $this->paramsTokenService->analyze($this->request->input('token'));
+ return $this->success($params);
+ }
+}
\ No newline at end of file
diff --git a/app/Controller/PaymentController.php b/app/Controller/PaymentController.php
new file mode 100644
index 0000000..b2c6fba
--- /dev/null
+++ b/app/Controller/PaymentController.php
@@ -0,0 +1,135 @@
+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),
+ '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
new file mode 100644
index 0000000..10fe872
--- /dev/null
+++ b/app/Controller/TestController.php
@@ -0,0 +1,81 @@
+get(TaskExecutor::class);
+ // $result = $exec->execute(new Task([MethodTask::class, 'handle'], [Coroutine::id()]));
+
+ // $client = ApplicationContext::getContainer()->get(SSDBTask::class);
+ // $result = $client->exec("set","bar","1234");
+ // $result = $client->exec("get","bar");
+
+ // $client = ApplicationContext::getContainer()->get(MethodTask::class);
+ // $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']);
+ //
+ // //$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)
+ {
+ $this->name = 'index2 action';
+ return $this->name;
+ }
+
+ public function index3(RequestInterface $request)
+ {
+ return $this->name;
+ }
+}
\ No newline at end of file
diff --git a/app/Controller/UserController.php b/app/Controller/UserController.php
new file mode 100644
index 0000000..d494d83
--- /dev/null
+++ b/app/Controller/UserController.php
@@ -0,0 +1,31 @@
+request->input('openid','');
+ $unionid = $this->request->input('unionid','');
+
+ return $this->success($this->userService->saveUserUnionid($openid,$unionid));
+ }
+
+}
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/SsdbExceptionHandler.php b/app/Exception/Handler/SsdbExceptionHandler.php
new file mode 100644
index 0000000..118d5ef
--- /dev/null
+++ b/app/Exception/Handler/SsdbExceptionHandler.php
@@ -0,0 +1,33 @@
+stopPropagation();
+
+ $content = json_encode([
+ "status" => 'error',
+ "code" => $throwable->getCode(),
+ "result" => [],
+ "message" => $throwable->getMessage()
+ ]);
+
+ return $response->withHeader('Content-Type', 'application/json')->withBody(new SwooleStream($content));
+ }
+
+ public function isValid(Throwable $throwable): bool
+ {
+ return $throwable instanceof SsdbException;
+ }
+}
\ 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/Exception/SsdbException.php b/app/Exception/SsdbException.php
new file mode 100644
index 0000000..c005c38
--- /dev/null
+++ b/app/Exception/SsdbException.php
@@ -0,0 +1,18 @@
+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/Libs/SimpleSSDB.php b/app/Libs/SimpleSSDB.php
new file mode 100644
index 0000000..c829bda
--- /dev/null
+++ b/app/Libs/SimpleSSDB.php
@@ -0,0 +1,622 @@
+easy();
+ }
+}
+
+class SSDB_Response
+{
+ public $cmd;
+ public $code;
+ public $data = null;
+ public $message;
+
+ function __construct($code='ok', $data_or_message=null){
+ $this->code = $code;
+ if($code == 'ok'){
+ $this->data = $data_or_message;
+ }else{
+ $this->message = $data_or_message;
+ }
+ }
+
+ function __toString(){
+ if($this->code == 'ok'){
+ $s = $this->data === null? '' : json_encode($this->data);
+ }else{
+ $s = $this->message;
+ }
+ return sprintf('%-13s %12s %s', $this->cmd, $this->code, $s);
+ }
+
+ function ok(){
+ return $this->code == 'ok';
+ }
+
+ function not_found(){
+ return $this->code == 'not_found';
+ }
+}
+
+// Depricated, use SimpleSSDB instead!
+class SSDB
+{
+ private $debug = false;
+ public $sock = null;
+ private $_closed = false;
+ private $recv_buf = '';
+ private $_easy = false;
+ public $last_resp = null;
+
+ function __construct($host, $port, $timeout_ms=2000){
+ $timeout_f = (float)$timeout_ms/1000;
+ $this->sock = @stream_socket_client("$host:$port", $errno, $errstr, $timeout_f);
+ if(!$this->sock){
+ throw new SSDBException("$errno: $errstr");
+ }
+ $timeout_sec = intval($timeout_ms/1000);
+ $timeout_usec = ($timeout_ms - $timeout_sec * 1000) * 1000;
+ @stream_set_timeout($this->sock, $timeout_sec, $timeout_usec);
+ if(function_exists('stream_set_chunk_size')){
+ @stream_set_chunk_size($this->sock, 1024 * 1024);
+ }
+ }
+
+ function set_timeout($timeout_ms){
+ $timeout_sec = intval($timeout_ms/1000);
+ $timeout_usec = ($timeout_ms - $timeout_sec * 1000) * 1000;
+ @stream_set_timeout($this->sock, $timeout_sec, $timeout_usec);
+ }
+
+ /**
+ * After this method invoked with yesno=true, all requesting methods
+ * will not return a SSDB_Response object.
+ * And some certain methods like get/zget will return false
+ * when response is not ok(not_found, etc)
+ */
+ function easy(){
+ $this->_easy = true;
+ }
+
+ function close(){
+ if(!$this->_closed){
+ @fclose($this->sock);
+ $this->_closed = true;
+ $this->sock = null;
+ }
+ }
+
+ function closed(){
+ return $this->_closed;
+ }
+
+ private $batch_mode = false;
+ private $batch_cmds = array();
+
+ function batch(){
+ $this->batch_mode = true;
+ $this->batch_cmds = array();
+ return $this;
+ }
+
+ function multi(){
+ return $this->batch();
+ }
+
+ function exec(){
+ $ret = array();
+ foreach($this->batch_cmds as $op){
+ list($cmd, $params) = $op;
+ $this->send_req($cmd, $params);
+ }
+ foreach($this->batch_cmds as $op){
+ list($cmd, $params) = $op;
+ $resp = $this->recv_resp($cmd, $params);
+ $resp = $this->check_easy_resp($cmd, $resp);
+ $ret[] = $resp;
+ }
+ $this->batch_mode = false;
+ $this->batch_cmds = array();
+ return $ret;
+ }
+
+ function request(){
+ $args = func_get_args();
+ $cmd = array_shift($args);
+ return $this->__call($cmd, $args);
+ }
+
+ private $async_auth_password = null;
+
+ function auth($password){
+ $this->async_auth_password = $password;
+ return null;
+ }
+
+ function __call($cmd, $params=array()){
+ $cmd = strtolower($cmd);
+ if($this->async_auth_password !== null){
+ $pass = $this->async_auth_password;
+ $this->async_auth_password = null;
+ $auth = $this->__call('auth', array($pass));
+ if($auth !== true){
+ throw new Exception("Authentication failed");
+ }
+ }
+
+ if($this->batch_mode){
+ $this->batch_cmds[] = array($cmd, $params);
+ return $this;
+ }
+
+ try{
+ if($this->send_req($cmd, $params) === false){
+ $resp = new SSDB_Response('error', 'send error');
+ }else{
+ $resp = $this->recv_resp($cmd, $params);
+ }
+ }catch(SSDBException $e){
+ if($this->_easy){
+ throw $e;
+ }else{
+ $resp = new SSDB_Response('error', $e->getMessage());
+ }
+ }
+
+ if($resp->code == 'noauth'){
+ $msg = $resp->message;
+ throw new Exception($msg);
+ }
+
+ $resp = $this->check_easy_resp($cmd, $resp);
+ return $resp;
+ }
+
+ private function check_easy_resp($cmd, $resp){
+ $this->last_resp = $resp;
+ if($this->_easy){
+ if($resp->not_found()){
+ return NULL;
+ }else if(!$resp->ok() && !is_array($resp->data)){
+ return false;
+ }else{
+ return $resp->data;
+ }
+ }else{
+ $resp->cmd = $cmd;
+ return $resp;
+ }
+ }
+
+ function multi_set($kvs=array()){
+ $args = array();
+ foreach($kvs as $k=>$v){
+ $args[] = $k;
+ $args[] = $v;
+ }
+ return $this->__call(__FUNCTION__, $args);
+ }
+
+ function multi_hset($name, $kvs=array()){
+ $args = array($name);
+ foreach($kvs as $k=>$v){
+ $args[] = $k;
+ $args[] = $v;
+ }
+ return $this->__call(__FUNCTION__, $args);
+ }
+
+ function multi_zset($name, $kvs=array()){
+ $args = array($name);
+ foreach($kvs as $k=>$v){
+ $args[] = $k;
+ $args[] = $v;
+ }
+ return $this->__call(__FUNCTION__, $args);
+ }
+
+ function incr($key, $val=1){
+ $args = func_get_args();
+ return $this->__call(__FUNCTION__, $args);
+ }
+
+ function decr($key, $val=1){
+ $args = func_get_args();
+ return $this->__call(__FUNCTION__, $args);
+ }
+
+ function zincr($name, $key, $score=1){
+ $args = func_get_args();
+ return $this->__call(__FUNCTION__, $args);
+ }
+
+ function zdecr($name, $key, $score=1){
+ $args = func_get_args();
+ return $this->__call(__FUNCTION__, $args);
+ }
+
+ function zadd($key, $score, $value){
+ $args = array($key, $value, $score);
+ return $this->__call('zset', $args);
+ }
+
+ function zRevRank($name, $key){
+ $args = func_get_args();
+ return $this->__call("zrrank", $args);
+ }
+
+ function zRevRange($name, $offset, $limit){
+ $args = func_get_args();
+ return $this->__call("zrrange", $args);
+ }
+
+ function hincr($name, $key, $val=1){
+ $args = func_get_args();
+ return $this->__call(__FUNCTION__, $args);
+ }
+
+ function hdecr($name, $key, $val=1){
+ $args = func_get_args();
+ return $this->__call(__FUNCTION__, $args);
+ }
+
+ private function send_req($cmd, $params){
+ $req = array($cmd);
+ foreach($params as $p){
+ if(is_array($p)){
+ $req = array_merge($req, $p);
+ }else{
+ $req[] = $p;
+ }
+ }
+ return $this->send($req);
+ }
+
+ private function recv_resp($cmd, $params){
+ $resp = $this->recv();
+ if($resp === false){
+ return new SSDB_Response('error', 'Unknown error');
+ }else if(!$resp){
+ return new SSDB_Response('disconnected', 'Connection closed');
+ }
+ if($resp[0] == 'noauth'){
+ $errmsg = isset($resp[1])? $resp[1] : '';
+ return new SSDB_Response($resp[0], $errmsg);
+ }
+ switch($cmd){
+ case 'dbsize':
+ case 'ping':
+ case 'qset':
+ case 'getbit':
+ case 'setbit':
+ case 'countbit':
+ case 'strlen':
+ case 'set':
+ case 'setx':
+ case 'setnx':
+ case 'zset':
+ case 'hset':
+ case 'qpush':
+ case 'qpush_front':
+ case 'qpush_back':
+ case 'qtrim_front':
+ case 'qtrim_back':
+ case 'del':
+ case 'zdel':
+ case 'hdel':
+ case 'hsize':
+ case 'zsize':
+ case 'qsize':
+ case 'hclear':
+ case 'zclear':
+ case 'qclear':
+ case 'multi_set':
+ case 'multi_del':
+ case 'multi_hset':
+ case 'multi_hdel':
+ case 'multi_zset':
+ case 'multi_zdel':
+ case 'incr':
+ case 'decr':
+ case 'zincr':
+ case 'zdecr':
+ case 'hincr':
+ case 'hdecr':
+ case 'zget':
+ case 'zrank':
+ case 'zrrank':
+ case 'zcount':
+ case 'zsum':
+ case 'zremrangebyrank':
+ case 'zremrangebyscore':
+ case 'ttl':
+ case 'expire':
+ if($resp[0] == 'ok'){
+ $val = isset($resp[1])? intval($resp[1]) : 0;
+ return new SSDB_Response($resp[0], $val);
+ }else{
+ $errmsg = isset($resp[1])? $resp[1] : '';
+ return new SSDB_Response($resp[0], $errmsg);
+ }
+ case 'zavg':
+ if($resp[0] == 'ok'){
+ $val = isset($resp[1])? floatval($resp[1]) : (float)0;
+ return new SSDB_Response($resp[0], $val);
+ }else{
+ $errmsg = isset($resp[1])? $resp[1] : '';
+ return new SSDB_Response($resp[0], $errmsg);
+ }
+ case 'get':
+ case 'substr':
+ case 'getset':
+ case 'hget':
+ case 'qget':
+ case 'qfront':
+ case 'qback':
+ if($resp[0] == 'ok'){
+ if(count($resp) == 2){
+ return new SSDB_Response('ok', $resp[1]);
+ }else{
+ return new SSDB_Response('server_error', 'Invalid response');
+ }
+ }else{
+ $errmsg = isset($resp[1])? $resp[1] : '';
+ return new SSDB_Response($resp[0], $errmsg);
+ }
+ break;
+ case 'qpop':
+ case 'qpop_front':
+ case 'qpop_back':
+ if($resp[0] == 'ok'){
+ $size = 1;
+ if(isset($params[1])){
+ $size = intval($params[1]);
+ }
+ if($size <= 1){
+ if(count($resp) == 2){
+ return new SSDB_Response('ok', $resp[1]);
+ }else{
+ return new SSDB_Response('server_error', 'Invalid response');
+ }
+ }else{
+ $data = array_slice($resp, 1);
+ return new SSDB_Response('ok', $data);
+ }
+ }else{
+ $errmsg = isset($resp[1])? $resp[1] : '';
+ return new SSDB_Response($resp[0], $errmsg);
+ }
+ break;
+ case 'keys':
+ case 'zkeys':
+ case 'hkeys':
+ case 'hlist':
+ case 'zlist':
+ case 'qslice':
+ if($resp[0] == 'ok'){
+ $data = array();
+ if($resp[0] == 'ok'){
+ $data = array_slice($resp, 1);
+ }
+ return new SSDB_Response($resp[0], $data);
+ }else{
+ $errmsg = isset($resp[1])? $resp[1] : '';
+ return new SSDB_Response($resp[0], $errmsg);
+ }
+ case 'auth':
+ case 'exists':
+ case 'hexists':
+ case 'zexists':
+ if($resp[0] == 'ok'){
+ if(count($resp) == 2){
+ return new SSDB_Response('ok', (bool)$resp[1]);
+ }else{
+ return new SSDB_Response('server_error', 'Invalid response');
+ }
+ }else{
+ $errmsg = isset($resp[1])? $resp[1] : '';
+ return new SSDB_Response($resp[0], $errmsg);
+ }
+ break;
+ case 'multi_exists':
+ case 'multi_hexists':
+ case 'multi_zexists':
+ if($resp[0] == 'ok'){
+ if(count($resp) % 2 == 1){
+ $data = array();
+ for($i=1; $idebug){
+ echo '> ' . str_replace(array("\r", "\n"), array('\r', '\n'), $s) . "\n";
+ }
+ try{
+ while(true){
+ $ret = @fwrite($this->sock, $s);
+ if($ret === false || $ret === 0){
+ $this->close();
+ throw new SSDBException('Connection lost');
+ }
+ $s = substr($s, $ret);
+ if(strlen($s) == 0){
+ break;
+ }
+ @fflush($this->sock);
+ }
+ }catch(Exception $e){
+ $this->close();
+ throw new SSDBException($e->getMessage());
+ }
+ return $ret;
+ }
+
+ function recv(){
+ $this->step = self::STEP_SIZE;
+ while(true){
+ $ret = $this->parse();
+ if($ret === null){
+ try{
+ $data = @fread($this->sock, 1024 * 1024);
+ if($this->debug){
+ echo '< ' . str_replace(array("\r", "\n"), array('\r', '\n'), $data) . "\n";
+ }
+ }catch(Exception $e){
+ $data = '';
+ }
+ if($data === false || $data === ''){
+ if(feof($this->sock)){
+ $this->close();
+ throw new SSDBException('Connection lost');
+ }else{
+ throw new SSDBTimeoutException('Connection timeout');
+ }
+ }
+ $this->recv_buf .= $data;
+# echo "read " . strlen($data) . " total: " . strlen($this->recv_buf) . "\n";
+ }else{
+ return $ret;
+ }
+ }
+ }
+
+ const STEP_SIZE = 0;
+ const STEP_DATA = 1;
+ public $resp = array();
+ public $step;
+ public $block_size;
+
+ private function parse(){
+ $spos = 0;
+ $epos = 0;
+ $buf_size = strlen($this->recv_buf);
+ // performance issue for large reponse
+ //$this->recv_buf = ltrim($this->recv_buf);
+ while(true){
+ $spos = $epos;
+ if($this->step === self::STEP_SIZE){
+ $epos = strpos($this->recv_buf, "\n", $spos);
+ if($epos === false){
+ break;
+ }
+ $epos += 1;
+ $line = substr($this->recv_buf, $spos, $epos - $spos);
+ $spos = $epos;
+
+ $line = trim($line);
+ if(strlen($line) == 0){ // head end
+ $this->recv_buf = substr($this->recv_buf, $spos);
+ $ret = $this->resp;
+ $this->resp = array();
+ return $ret;
+ }
+ $this->block_size = intval($line);
+ $this->step = self::STEP_DATA;
+ }
+ if($this->step === self::STEP_DATA){
+ $epos = $spos + $this->block_size;
+ if($epos <= $buf_size){
+ $n = strpos($this->recv_buf, "\n", $epos);
+ if($n !== false){
+ $data = substr($this->recv_buf, $spos, $epos - $spos);
+ $this->resp[] = $data;
+ $epos = $n + 1;
+ $this->step = self::STEP_SIZE;
+ continue;
+ }
+ }
+ break;
+ }
+ }
+
+ // packet not ready
+ if($spos > 0){
+ $this->recv_buf = substr($this->recv_buf, $spos);
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/app/Listener/ValidatorFactoryResolvedListener.php b/app/Listener/ValidatorFactoryResolvedListener.php
index 0bfb047..75b3a36 100644
--- a/app/Listener/ValidatorFactoryResolvedListener.php
+++ b/app/Listener/ValidatorFactoryResolvedListener.php
@@ -48,6 +48,36 @@ class ValidatorFactoryResolvedListener implements ListenerInterface
return $message;
});
+ // 注册了 base64 验证器规则
+ $validatorFactory->extend('base64', function ($attribute, $value, $parameters, $validator) {
+
+ preg_match('/^(data:\s*image\/(\w+);base64,)/', $value, $result);
+
+ if (empty($result)) {
+ return false;
+ }
+
+ if (
+ in_array('image', $parameters)
+ && !in_array($result[2], ['jpg','jpeg','png','gif','svg','bmp'])
+ ) {
+ return false;
+ }
+
+ return true;
+
+ });
+
+ // 注册了 ext_not_in 验证器规则
+ $validatorFactory->extend('ext_not_in', function ($attribute, $value, $parameters, $validator) {
+
+ if (empty($parameters)) {
+ $parameters = ['', 'php', 'exe', 'sql', 'sh', 'bat', 'py', 'go', 'c', 'cpp'];
+ }
+ return !in_array($value->getExtension(), $parameters);
+
+ });
+
// 注册了 exists_enable 验证器规则,参数是table,field,where1,where2...
$validatorFactory->extend('exists_enable', function ($attribute, $value, $parameters, $validator) {
@@ -79,6 +109,5 @@ class ValidatorFactoryResolvedListener implements ListenerInterface
return $builder->exists();
});
-
}
}
\ No newline at end of file
diff --git a/app/Middleware/Auth/ApiMiddleware.php b/app/Middleware/Auth/ApiMiddleware.php
index ce06bff..44cf3fc 100644
--- a/app/Middleware/Auth/ApiMiddleware.php
+++ b/app/Middleware/Auth/ApiMiddleware.php
@@ -39,7 +39,7 @@ class ApiMiddleware implements MiddlewareInterface
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
- if (env('APP_ENV') == 'dev') {
+ if (env('APP_ENV') == 'dev' || env('APP_ENV') == 'local') {
return $handler->handle($request);
}
diff --git a/app/Middleware/CorsMiddleware.php b/app/Middleware/CorsMiddleware.php
new file mode 100644
index 0000000..7fb6495
--- /dev/null
+++ b/app/Middleware/CorsMiddleware.php
@@ -0,0 +1,34 @@
+withHeader('Access-Control-Allow-Origin', '*')
+ ->withHeader('Access-Control-Allow-Credentials', 'true')
+ // Headers 可以根据实际情况进行改写。
+ ->withHeader('Access-Control-Allow-Headers', 'DNT,Keep-Alive,User-Agent,Cache-Control,Content-Type,Authorization');
+
+ Context::set(ResponseInterface::class, $response);
+
+ if ($request->getMethod() == 'OPTIONS') {
+ return $response;
+ }
+
+ return $handler->handle($request);
+ }
+}
\ No newline at end of file
diff --git a/app/Model/Ad.php b/app/Model/Ad.php
new file mode 100644
index 0000000..71cdbf9
--- /dev/null
+++ b/app/Model/Ad.php
@@ -0,0 +1,70 @@
+ 'page',
+ 2 => 'webview',
+ 3 => 'applet',
+ ];
+
+ /**
+ * 类型 1 = 首页banners
+ */
+ const TYPE_BANNER = 1;
+
+ /**
+ * 启用状态
+ */
+ const STATUS_YES = 1;
+ const STATUS_NO = 2;
+
+ /**
+ * The table associated with the model.
+ *
+ * @var string
+ */
+ protected $table = 'ims_cjdc_ad';
+ /**
+ * The attributes that are mass assignable.
+ *
+ * @var array
+ */
+ protected $fillable = [];
+ /**
+ * The attributes that should be cast to native types.
+ *
+ * @var array
+ */
+ protected $casts = [];
+
+ protected $appends = [
+ 'item_text',
+ 'redirect_url'
+ ];
+
+ /**
+ * 获取跳转说明
+ */
+ public function getItemTextAttribute()
+ {
+ return self::ITEM[$this->item];
+ }
+
+ /**
+ * 获取跳转连接
+ */
+ public function getRedirectUrlAttribute()
+ {
+ return $this->src ?: $this->src2;
+ }
+}
\ 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..72c42de
--- /dev/null
+++ b/app/Model/Store.php
@@ -0,0 +1,10 @@
+ '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/CouponGetListRequest.php b/app/Request/CouponGetListRequest.php
new file mode 100644
index 0000000..252a45c
--- /dev/null
+++ b/app/Request/CouponGetListRequest.php
@@ -0,0 +1,45 @@
+ 'required|nonempty|integer|exists_enable:ims_cjdc_user,id',
+ 'receive_type' => 'required|nonempty|integer'
+ ];
+ }
+
+ public function messages(): array
+ {
+ return [
+ 'user_id.*' => ':attribute信息不正确',
+ 'receive_type.*' => ':attribute必须'
+ ];
+ }
+
+ public function attributes(): array
+ {
+ return [
+ 'user_id' => '用户ID',
+ 'receive_type' => '领取方式'
+ ];
+ }
+}
diff --git a/app/Request/CouponRebateReceiveRequest.php b/app/Request/CouponRebateReceiveRequest.php
new file mode 100644
index 0000000..7abce7d
--- /dev/null
+++ b/app/Request/CouponRebateReceiveRequest.php
@@ -0,0 +1,45 @@
+ 'required|nonempty|integer|exists_enable:ims_cjdc_user,id',
+ 'receive_type' => 'required|nonempty|integer',
+ ];
+ }
+
+ public function messages(): array
+ {
+ return [
+ 'user_id.*' => ':attribute信息不正确',
+ 'receive_type.*' => ':attribute必须',
+ ];
+ }
+
+ public function attributes(): array
+ {
+ return [
+ 'user_id' => '领取用户ID',
+ 'receive_type' => '领取方式',
+ ];
+ }
+}
diff --git a/app/Request/CouponRebateTieRequest.php b/app/Request/CouponRebateTieRequest.php
new file mode 100644
index 0000000..39a5863
--- /dev/null
+++ b/app/Request/CouponRebateTieRequest.php
@@ -0,0 +1,48 @@
+ 'required|nonempty',
+ 'coupon_forward_ids' => 'required|nonempty',
+ 'coupon_repay_id' => 'required|nonempty',
+ ];
+ }
+
+ public function messages(): array
+ {
+ return [
+ 'coupon_activity.*' => ':attribute必须',
+ 'coupon_forward_ids.*' => ':attribute必须',
+ 'coupon_repay_id.*' => ':attribute必须',
+ ];
+ }
+
+ public function attributes(): array
+ {
+ return [
+ 'coupon_activity' => '优惠券活动',
+ 'coupon_forward_ids' => '领取类型优惠券',
+ 'coupon_repay_id' => '返还类型优惠券',
+ ];
+ }
+}
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/UserUnionidRequest.php b/app/Request/UserUnionidRequest.php
new file mode 100644
index 0000000..939940f
--- /dev/null
+++ b/app/Request/UserUnionidRequest.php
@@ -0,0 +1,45 @@
+ 'required|nonempty',
+ 'unionid' => 'required|nonempty',
+ ];
+ }
+
+ public function messages(): array
+ {
+ return [
+ 'openid.*' => ':attribute必须',
+ 'unionid.*' => ':attribute必须',
+ ];
+ }
+
+ public function attributes(): array
+ {
+ return [
+ 'openid' => '用户openid',
+ 'unionid' => '用户unionid',
+ ];
+ }
+}
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/AdService.php b/app/Service/AdService.php
new file mode 100644
index 0000000..997e9a8
--- /dev/null
+++ b/app/Service/AdService.php
@@ -0,0 +1,22 @@
+select(['id','title','logo','item','src','src2'])
+ ->where([
+ 'type' => Ad::TYPE_BANNER,
+ 'status' => Ad::STATUS_YES
+ ])
+ ->orderBy('orderby', 'desc')
+ ->get();
+ }
+}
\ No newline at end of file
diff --git a/app/Service/AdServiceInterface.php b/app/Service/AdServiceInterface.php
new file mode 100644
index 0000000..f488422
--- /dev/null
+++ b/app/Service/AdServiceInterface.php
@@ -0,0 +1,11 @@
+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 @@
+0领取失败
+ $result = [
+ 'status' => 1,
+ 'coupon_text' => '活动已过期~'
+ ];
+ /* 如果请求的优惠券ids为空,则返回过期提示 */
+ if($this->commonService->empty($params["ids"])){
+ return $result;
+ }
+
+ $ids = $params["ids"];
+ $idsData = is_array($ids) ? $ids : explode(',',$ids);
+
+ // 错误日志记录
+ $errorData = [
+ 'coupon_ids' =>$ids,
+ 'user_id' =>$userId,
+ 'receiveType' =>$receiveType,
+ 'sendUserId' =>$sendUserId,
+ 'phone' =>$phone
+ ];
+
+ $ssdb = ApplicationContext::getContainer()->get(SSDBTask::class);
+ $activity = $ssdb->exec('hget', SsdbKeysPrefix::COUPON_REBATE_ACTIVITY ,'activity');
+ // ssdb 键值
+ $ssdbKey = 'activity_'.$activity.'_user_'.$userId;
+ $receiveSsdb = [];
+
+ // 判断是否已全部领取过
+ $userReceive = $ssdb->exec('hget', SsdbKeysPrefix::COUPON_REBATE_RECEIVE,$ssdbKey);
+ if($userReceive === false){
+ $ids = $idsData;
+ }else{
+ $userReceiveCouponIds = empty($userReceive) ? [] : explode(',',$userReceive) ;
+ $ids = array_diff($idsData, $userReceiveCouponIds);
+ $receiveSsdb = $userReceiveCouponIds;
+ }
+
+ if(count($ids) > 0){
+ try{
+ Db::transaction( function() use ($ids,$receiveType,$userId,$sendUserId,$phone,&$result,&$errorData,&$receiveSsdb) {
+
+ $now = time();
+ $success = [];
+
+ //获取优惠券信息 (读写锁,完全控制,性能低)
+ $coupons = Coupon::whereIn('id', $ids)->lockForUpdate()
+ ->where('active_type',2)
+ ->where('status',1)
+ ->where('start_time', '<=', $now)
+ ->where('end_time', '>=', $now)
+ ->whereRaw('inventory > inventory_use')
+ ->select('id','title','inventory','inventory_use','full_amount','discounts','active_type')
+ ->get();
+
+ foreach($coupons as $coupon){
+ $errorData['coupon_id'] = $coupon->id;
+
+ // 查询一次能领取的数量
+ $couponReceiveType = CouponUserRecType::where('system_coupon_user_id',$coupon->id)->select('one_receive_number');
+ if (env('SUB_CHANNEL') == 1) {
+ $couponReceiveType->where('receive_type',$receiveType);
+ }
+ $couponReceiveType = $couponReceiveType->first();
+
+ // 优惠券可领取数量 >= 本次领取数量
+ if($coupon->inventory - $coupon->inventory_use >= $couponReceiveType->one_receive_number){
+
+ // 判断是否领取过 存在记录则领取过
+ $isReceive = CouponRec::select('id')
+ ->where('system_coupon_user_id',$coupon->id)
+ ->where('user_id',$userId)
+ ->exists();
+
+ if(!$isReceive){
+ //记录已领取的数量
+ $coupon->inventory_use += $couponReceiveType->one_receive_number;
+
+ $couponReceive = new CouponRec;
+ $couponReceive->user_id = $userId;
+ $couponReceive->system_coupon_user_id = $coupon->id;
+ $couponReceive->order_main_id = 0;
+ $couponReceive->receive_time = $now;
+ $couponReceive->number = $couponReceiveType->one_receive_number;
+ $couponReceive->number_remain = $couponReceiveType->one_receive_number;
+ $couponReceive->status = 0;
+ $couponReceive->update_time = $now;
+ $couponReceive->receive_type = $receiveType;
+ $couponReceive->send_user_id = $sendUserId;
+ $couponReceive->phone = $phone;
+
+ if ( $couponReceive->save() && $coupon->save() ) {
+ $success[] = $coupon;
+ $receiveSsdb[] = $coupon->id;
+ }else{
+ $errorData['msg'] = '添加优惠券到用户领取表或者记录已领取数量失败';
+ $this->log->event(
+ LogLabel::COUPON_LOG,
+ $errorData
+ );
+ }
+ }
+ }
+ }
+ if(count($success) > 0){
+ $result['status'] = 0;
+ $result['coupon_text'] = '恭喜您领取成功!';
+ }
+ });
+ } catch (Exception $e){
+ $errorData['msg'] = $e->getMessage();
+ $this->log->event(
+ LogLabel::COUPON_LOG,
+ $errorData
+ );
+ }
+
+ if(count($receiveSsdb) > 0){
+
+ $saveSsdb = [
+ $ssdbKey,
+ implode(',',$receiveSsdb)
+ ];
+ if(false === $ssdb->exec('hset',SsdbKeysPrefix::COUPON_REBATE_RECEIVE, $saveSsdb)){
+ $errorData['msg'] = '记录领取优惠券到ssdb失败';
+ $this->log->event(
+ LogLabel::COUPON_LOG,
+ $errorData
+ );
+ };
+ }
+ }else{
+ $result['status'] = 2;
+ $result['coupon_text'] = '您已领取!赶快去下单吧~';
+ }
+
+ return $result;
+ }
+
+ /*
+ * 判断用户是否已领取过优惠券
+ * */
+ public function isCouponRebate($user_id)
+ {
+ $ssdb = ApplicationContext::getContainer()->get(SSDBTask::class);
+ $activity = $ssdb->exec('hget', SsdbKeysPrefix::COUPON_REBATE_ACTIVITY ,'activity');
+ // ssdb 键值
+ $ssdbKey = 'activity_'.$activity.'_user_'.$user_id;
+
+ // 判断是否已全部领取过
+ $userReceive = $ssdb->exec('hget', SsdbKeysPrefix::COUPON_REBATE_RECEIVE,$ssdbKey);
+ if($userReceive === false || is_null($userReceive)){
+ return false;
+ }else{
+ return $userReceive;
+ }
+
+ }
+
+ /*
+ *获取活动信息
+ */
+ public function getActiveInfo()
+ {
+ //获取SSDB上的活动信息
+ $ssdb = ApplicationContext::getContainer()->get(SSDBTask::class);
+ $active = $ssdb->exec('hgetall',SsdbKeysPrefix::COUPON_REBATE_ACTIVITY);
+ $coupon_ids = explode(',',$active['forward']);
+ $time = time();
+ $res = Db::table('ims_system_coupon_user')
+ ->whereIn('id',$coupon_ids)
+ ->where([
+ ['status', '=', 1],
+ ['active_type', '=', 2],
+ ['start_time', '<=', $time],
+ ['end_time', '>', $time],
+ ])
+ ->whereRaw('inventory > inventory_use')
+ ->orderBy('weigh', 'desc')
+ ->orderBy('addtime', 'desc')
+ ->get();
+ return $res;
+ }
+
+ /**
+ * 将优惠券绑定活动
+ * 领取优惠券 COUPON_REBATE_FORWARD 可多张
+ * 返还优惠券 COUPON_REBATE_REPAY 只一张
+ */
+ public function tieCouponActive($couponActivity, $couponForward, $couponRepay)
+ {
+
+ $ssdb = ApplicationContext::getContainer()->get(SSDBTask::class);
+
+ $data = [
+ 'activity', $couponActivity,
+ 'forward' , $couponForward,
+ 'repay' , $couponRepay
+ ];
+
+ $result = [
+ 'result' => ($ssdb->exec('multi_hset', SsdbKeysPrefix::COUPON_REBATE_ACTIVITY, $data) === false) ? false : true ,
+ 'data' => $ssdb->exec('hgetall', SsdbKeysPrefix::COUPON_REBATE_ACTIVITY)
+ ];
+
+ return $result;
+ }
+
+ /*
+ * 支付成功 返券
+ */
+ public function couponRebate($order_id)
+ {
+ //获取SSDB上的活动信息
+ $ssdb = ApplicationContext::getContainer()->get(SSDBTask::class);
+ $active = $ssdb->execWithoutTask('hgetall',SsdbKeysPrefix::COUPON_REBATE_ACTIVITY);
+ //判断返券优惠券是否有库存
+ $inventory = Db::table('ims_system_coupon_user')
+ ->where('id',$active['repay'])
+ ->whereRaw('inventory > inventory_use')
+ ->exists();
+ if(!$inventory){
+ //库存不足
+ return false;
+ }
+
+ //获取活动发放优惠券id
+ $coupon_ids = explode(',',$active['forward']);
+
+ /* 判断被使用的优惠券类型是否为转发活动优惠券 */
+ $coupon = Db::table('ims_system_coupon_user_receive as r')
+ ->leftjoin('ims_system_coupon_user_use as u', 'u.user_receive_id', '=', 'r.id')
+ ->where([
+ ['u.order_main_id', '=', $order_id],
+ ['r.send_user_id', '>', 0],
+ ['r.rebate_type', '=', 1],
+ ['r.receive_type', '=', 4],
+ ])
+ ->whereIn('r.system_coupon_user_id',$coupon_ids)
+ ->select('r.user_id', 'r.send_user_id')
+ ->first();
+ /* 如果使用的优惠券为转发活动优惠券
+ **则给赠送者返一张优惠券
+ * **自己给自己转发的券不给返券
+ */
+ if ($coupon && ($coupon->user_id != $coupon->send_user_id)) {
+ //是否已返过券
+ $exists_coupon_rebate = Db::table('ims_system_coupon_user_receive')
+ ->where([
+ ['system_coupon_user_id' ,'=', $active['repay']],
+ ['user_id' ,'=', $coupon->send_user_id],
+ ['receive_type' ,'=', 5],
+ ])
+ ->select('id','status')
+ ->first();
+ //开启事务
+ Db::beginTransaction();
+ try {
+ //返券
+ if($exists_coupon_rebate){
+ //如果已有该优惠券 则领取数量 和 可用数量 自增1
+ Db::table('ims_system_coupon_user_receive')
+ ->where([
+ ['id' ,'=', $exists_coupon_rebate->id],
+ ])
+ ->increment('number');
+ Db::table('ims_system_coupon_user_receive')
+ ->where([
+ ['id' ,'=', $exists_coupon_rebate->id],
+ ])
+ ->increment('number_remain');
+ //如果该用户在领取表中 status为已用完状态 2 则改为已用部分 1
+ if($exists_coupon_rebate->status == 2){
+ Db::table('ims_system_coupon_user_receive')
+ ->where([
+ ['id' ,'=', $exists_coupon_rebate->id],
+ ])
+ ->update(['status' => 1]);;
+ }
+ }else {
+ //否则新增一条返券记录
+ $nowTime = time();
+ Db::table('ims_system_coupon_user_receive')->insert([
+ [
+ 'user_id' => $coupon->send_user_id,
+ 'system_coupon_user_id' => $active['repay'],
+ 'receive_type' => 5,
+ 'status' => 0,
+ 'number' => 1,
+ 'number_remain' => 1,
+ 'order_main_id' => $order_id,
+ 'receive_time' => $nowTime,
+ 'update_time' => $nowTime,
+ 'created_at' => $nowTime,
+ 'updated_at' => $nowTime,
+ ]
+ ]);
+ }
+ //首次返券更新rebate_type字段 防止重复返券
+ Db::table('ims_system_coupon_user_receive')
+ ->where([
+ ['user_id','=',$coupon->user_id],
+ ['receive_type','=',4],
+ ])
+ ->whereIn('system_coupon_user_id',$coupon_ids)
+ ->update(['rebate_type' => 2]);
+ //更新库存操作
+ Db::table('ims_system_coupon_user')
+ ->where('id', $active['repay'])
+ ->increment('inventory_use');
+
+ //添加领取记录到ssdb
+ $data = [
+ $order_id,$coupon->user_id,
+ ];
+ $ssdb->execWithoutTask('multi_hset', SsdbKeysPrefix::COUPON_REBATE_LIST.$coupon->send_user_id, $data);
+ // 提交
+ Db::commit();
+ } catch (\Exception $e) {
+ //写入日志文件
+ $log_Data = array();
+ $log_Data['name'] = '返券';
+ $log_Data['order_id'] = $order_id;
+ $log_Data['msg'] = '返券失败';
+ $this->log->event(
+ LogLabel::COUPON_LOG,
+ $log_Data
+ );
+ // 回滚
+ Db::rollBack();
+ }
+ }
+ return true;
+ }
+
+ /*
+ * 支付成功 返券
+ */
+ public function couponRebateInTask($order_id)
+ {
+ //获取SSDB上的活动信息
+ $ssdb = ApplicationContext::getContainer()->get(SSDBTask::class);
+ $active = $ssdb->exec('hgetall',SsdbKeysPrefix::COUPON_REBATE_ACTIVITY);
+ //判断返券优惠券是否有库存
+ $inventory = Db::table('ims_system_coupon_user')
+ ->where('id',$active['repay'])
+ ->whereRaw('inventory > inventory_use')
+ ->exists();
+ if(!$inventory){
+ //库存不足
+ return false;
+ }
+
+ //获取活动发放优惠券id
+ $coupon_ids = explode(',',$active['forward']);
+
+ /* 判断被使用的优惠券类型是否为转发活动优惠券 */
+ $coupon = Db::table('ims_system_coupon_user_receive as r')
+ ->leftjoin('ims_system_coupon_user_use as u', 'u.user_receive_id', '=', 'r.id')
+ ->where([
+ ['u.order_main_id', '=', $order_id],
+ ['r.send_user_id', '>', 0],
+ ['r.rebate_type', '=', 1],
+ ['r.receive_type', '=', 4],
+ ])
+ ->whereIn('r.system_coupon_user_id',$coupon_ids)
+ ->select('r.user_id', 'r.send_user_id')
+ ->first();
+ /* 如果使用的优惠券为转发活动优惠券
+ **则给赠送者返一张优惠券
+ * **自己给自己转发的券不给返券
+ */
+ if ($coupon && ($coupon->user_id != $coupon->send_user_id)) {
+ //是否已返过券
+ $exists_coupon_rebate = Db::table('ims_system_coupon_user_receive')
+ ->where([
+ ['system_coupon_user_id' ,'=', $active['repay']],
+ ['user_id' ,'=', $coupon->send_user_id],
+ ['receive_type' ,'=', 5],
+ ])
+ ->select('id','status')
+ ->first();
+ //开启事务
+ Db::beginTransaction();
+ try {
+ //返券
+ if($exists_coupon_rebate){
+ //如果已有该优惠券 则领取数量 和 可用数量 自增1
+ Db::table('ims_system_coupon_user_receive')
+ ->where([
+ ['id' ,'=', $exists_coupon_rebate->id],
+ ])
+ ->increment('number');
+ Db::table('ims_system_coupon_user_receive')
+ ->where([
+ ['id' ,'=', $exists_coupon_rebate->id],
+ ])
+ ->increment('number_remain');
+ //如果该用户在领取表中 status为已用完状态 2 则改为已用部分 1
+ if($exists_coupon_rebate->status == 2){
+ Db::table('ims_system_coupon_user_receive')
+ ->where([
+ ['id' ,'=', $exists_coupon_rebate->id],
+ ])
+ ->update(['status' => 1]);;
+ }
+ }else {
+ //否则新增一条返券记录
+ $nowTime = time();
+ Db::table('ims_system_coupon_user_receive')->insert([
+ [
+ 'user_id' => $coupon->send_user_id,
+ 'system_coupon_user_id' => $active['repay'],
+ 'receive_type' => 5,
+ 'status' => 0,
+ 'number' => 1,
+ 'number_remain' => 1,
+ 'order_main_id' => $order_id,
+ 'receive_time' => $nowTime,
+ 'update_time' => $nowTime,
+ 'created_at' => $nowTime,
+ 'updated_at' => $nowTime,
+ ]
+ ]);
+ }
+ //首次返券更新rebate_type字段 防止重复返券
+ Db::table('ims_system_coupon_user_receive')
+ ->where([
+ ['user_id','=',$coupon->user_id],
+ ['receive_type','=',4],
+ ])
+ ->whereIn('system_coupon_user_id',$coupon_ids)
+ ->update(['rebate_type' => 2]);
+ //更新库存操作
+ Db::table('ims_system_coupon_user')
+ ->where('id', $active['repay'])
+ ->increment('inventory_use');
+
+ //添加领取记录到ssdb
+ $data = [
+ $order_id,$coupon->user_id,
+ ];
+ $ssdb->exec('multi_hset', SsdbKeysPrefix::COUPON_REBATE_LIST.$coupon->send_user_id, $data);
+ // 提交
+ Db::commit();
+ } catch (\Exception $e) {
+ //写入日志文件
+ $log_Data = array();
+ $log_Data['name'] = '返券';
+ $log_Data['order_id'] = $order_id;
+ $log_Data['msg'] = '返券失败';
+ $this->log->event(
+ LogLabel::COUPON_LOG,
+ $log_Data
+ );
+ // 回滚
+ Db::rollBack();
+ }
+ }
+ return true;
+ }
+
+ /**
+ * 清优惠券领取记录(SSDB)
+ */
+ public function clearSsdbCouponReceiveByName($activity, $userId, $get = 0, $isAll = 0){
+ $key = 'activity_'.$activity.'_user_'.$userId;
+ $ssdb = ApplicationContext::getContainer()->get(SSDBTask::class);
+
+ if($isAll > 0){
+ if($get > 0){
+ return $ssdb->exec('hgetAll', SsdbKeysPrefix::COUPON_REBATE_RECEIVE);
+ }else{
+ return ( $ssdb->exec('hclear', SsdbKeysPrefix::COUPON_REBATE_RECEIVE) === false) ? false : true ;
+ }
+ }else {
+ if($get > 0){
+ return $ssdb->exec('hget', SsdbKeysPrefix::COUPON_REBATE_RECEIVE, $key);
+ }else{
+ return ( $ssdb->exec('hdel', SsdbKeysPrefix::COUPON_REBATE_RECEIVE, $key) === false) ? false : true ;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/Service/CouponRebateServiceInterface.php b/app/Service/CouponRebateServiceInterface.php
new file mode 100644
index 0000000..4208c28
--- /dev/null
+++ b/app/Service/CouponRebateServiceInterface.php
@@ -0,0 +1,24 @@
+get(SSDBTask::class);
+ $couponActivity = $ssdb->exec('hgetall', SsdbKeysPrefix::COUPON_REBATE_ACTIVITY);
+ $activityType = $couponActivity === false ? 0 : $couponActivity['activity'];
+
+ $result = [
+ 'active_type' => 1,
+ 'not_receive' => [],
+ 'jump_data' => [
+ 'src' => "/zh_cjdianc/pages/couponrebate/index?activity_type=".$activityType,
+ 'src2' => "/zh_cjdianc/pages/couponrebate/index?activity_type=".$activityType,
+ 'share_bg' => env('OSS_IMG_HOST').'/static/img/coupon_share.png',
+ 'receive_bg' => env('OSS_IMG_HOST').'/static/img/coupon_bg.png',
+ 'coupons' => []
+ ]
+ ];
+ $nowTime = time();
+ $c_ids = [];
+ $whereC = [
+ ['end_time','>',$nowTime],
+ ['start_time','<=',$nowTime],
+ ['status','=',1]
+ ];
+ // 渠道开启,查询该渠道可以领取的优惠券ID
+ // 渠道未开启,查询所有优惠券
+ if (env('SUB_CHANNEL') == 1) {
+ $c_ids = CouponUserRecType::where('receive_type', $receiveType)->where($whereC)->pluck('system_coupon_user_id');
+ } else {
+ $c_ids = Coupon::where($whereC)->pluck('id');
+ }
+
+ $couponReceive = CouponRec::where('user_id',$userId);
+
+ // 渠道开启,查询该用户在此渠道领过的优惠券ID
+ if (env('SUB_CHANNEL') == 1) {
+ $couponReceive->where('receive_type', $receiveType);
+ }
+ $cr_ids = $couponReceive->pluck('system_coupon_user_id');
+
+ // 可领取的券ID
+ $c_ids = $c_ids->toArray();
+ // 已经领取的券ID
+ $cr_ids = $cr_ids->toArray();
+
+ // 当前用户可领的优惠券ID
+ $couponIds = array_diff($c_ids, $cr_ids);
+
+ // 转发型优惠券
+ $couponReceiveIds = ($couponActivity === false || $this->commonService->empty($couponActivity['forward']) )? [] : explode(',',$couponActivity['forward']);
+ // 所有优惠券
+ $couponIds = array_merge($couponIds,$couponReceiveIds);
+
+ $whereC = [
+ ['u.end_time','>',$nowTime],
+ ['u.start_time','<=',$nowTime],
+ ['u.status','=',1]
+ ];
+ // 查询领取型1 和 转发型2
+ $whereActiveType = [1,2];
+
+ if (env('SUB_CHANNEL') == 1) {
+ array_push($whereC, ['type.receive_type','=', $receiveType]);
+ }
+
+ $coupons = Db::table('ims_system_coupon_user as u')
+ ->join('ims_system_coupon_user_receivetype as type', 'u.id', '=', 'type.system_coupon_user_id')
+ ->whereIn('u.id', $couponIds)
+ ->whereIn('u.active_type', $whereActiveType)
+ ->where($whereC)
+ ->whereRaw('u.inventory_use < u.inventory and u.inventory-u.inventory_use >= type.one_receive_number')
+ ->select('u.*','type.one_receive_number')
+ ->orderBy('u.weigh','desc')
+ ->get();
+
+ foreach ($coupons as $k => &$v){
+
+ if($v->active_type == 1 && count($result['not_receive']) < 4){
+ $result['not_receive'][] = $v;
+ }else if($v->active_type == 2 && in_array($v->id,$couponReceiveIds)){
+ $result['jump_data']['coupons'][] = $v->id;
+ }
+
+ if($v->discount_type == 2){
+ $v->discounts = floatval($v->discounts);
+ }
+ }
+
+ $result['active_type'] = count($result['jump_data']['coupons']) > 0 ? 2 : $result['active_type'] ;
+
+ return $result;
+ }
+
+ //统计用户
+ public function userCouponAccount()
+ {
+
+ }
+
+ /**
+ * 用户领取优惠卷
+ */
+ public function userReceiveCoupon()
+ {
+
+ }
+
+ /**
+ * 获取用户已经领取的优惠卷列表
+ */
+ public function getUserReceiveCouponList()
+ {
+
+ }
+
+ /**
+ * 获取用户当前订单可用的优惠券列表
+ * 按分类(1订单 等优惠)分组返回
+ */
+ public function getUserAvailableCoupons()
+ {
+
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getOrderCanUseCoupons($orderAmount, $marketId, $userId, $fields=[], $type = 1, $storeTypeIds = [0])
+ {
+
+ // 用户今日使用过的优惠券
+ $redis = ApplicationContext::getContainer()->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
new file mode 100644
index 0000000..bdd60c5
--- /dev/null
+++ b/app/Service/CouponServiceInterface.php
@@ -0,0 +1,30 @@
+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 = $order['type']==1 ? "{\"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'] = $order['type']==1 ? "您有新的懒族外卖订单" : "微信到账".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..3eab4a9
--- /dev/null
+++ b/app/Service/OrderService.php
@@ -0,0 +1,536 @@
+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) {
+
+ $this->log->event(
+ LogLabel::ORDER_LOG,
+ ['message' => $e->getMessage()]
+ );
+
+ 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) {
+ $this->log->event(
+ LogLabel::ORDER_LOG,
+ ['message' => $e->getMessage()]
+ );
+ 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 @@
+get(SSDBTask::class);
+
+ $kvs = [];
+ foreach ($params as $key => $value) {
+ $kvs[] = $key;
+ $kvs[] = $value;
+ }
+
+ if(false === $ssdb->exec('multi_hset', SsdbKeysPrefix::PARAMS_TOKEN.$token, $kvs)) {
+
+ $this->log->event(
+ LogLabel::SSDB_LOG,
+ ['method' => 'multi_hset', 'key' => SsdbKeysPrefix::PARAMS_TOKEN.$token, 'kvs' => $kvs]
+ );
+
+ throw new SsdbException(ErrorCode::SSDB_ERROR, 'token生成失败');
+ }
+
+ return $token;
+ }
+
+ /**
+ * 解析token获取对应数据参数
+ * @param $token
+ */
+ public function analyze($token)
+ {
+ $ssdb = ApplicationContext::getContainer()->get(SSDBTask::class);
+ $params = $ssdb->exec('hgetall', SsdbKeysPrefix::PARAMS_TOKEN.$token);
+
+ if (false === $params) {
+
+ $this->log->event(
+ LogLabel::SSDB_LOG,
+ ['method' => 'hgetall', 'key' => SsdbKeysPrefix::PARAMS_TOKEN.$token, 'params' => $params]
+ );
+
+ throw new SsdbException(ErrorCode::SSDB_ERROR, 'token解析失败');
+ }
+
+ if (empty($params)) {
+
+ $this->log->event(
+ LogLabel::SSDB_LOG,
+ ['method' => 'hgetall', 'key' => SsdbKeysPrefix::PARAMS_TOKEN.$token, 'params' => $params]
+ );
+
+ throw new SsdbException(ErrorCode::SSDB_ERROR, 'token不存在');
+ }
+
+ return $params;
+ }
+
+}
\ No newline at end of file
diff --git a/app/Service/UserService.php b/app/Service/UserService.php
new file mode 100644
index 0000000..b5cc5fb
--- /dev/null
+++ b/app/Service/UserService.php
@@ -0,0 +1,79 @@
+where(['user_id' => $user_id])
+ ->where('id', '!=', $order_main_id)
+ ->where(function ($query){
+ $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();
+
+ return !$exist;
+ }
+
+ /**
+ * 根据用户的openid更新unionid信息
+ * 如果没有找到用户,则不做任何处理
+ * @param $openid
+ * @param $unionid
+ * @return array
+ */
+ public function saveUserUnionid($openid,$unionid)
+ {
+ $result = [
+ 'status' => false,
+ 'msg' => '用户不存在或者已存在相同unionid'
+ ];
+
+ // 查询用户是否存在
+ $userinfo = Users::select('id','unionid')->where('openid',$openid)->first();
+ if($userinfo && $userinfo->unionid != $unionid){
+ $userinfo->unionid = $unionid;
+ if($res = $userinfo->save()){
+ $result['status'] = true;
+ $result['msg'] = '更改用户unionid信息成功';
+ $result['res'] = $res;
+ }else{
+ $result['msg'] = '更改用户unionid信息失败';
+ }
+ }
+
+ 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
new file mode 100644
index 0000000..2920d53
--- /dev/null
+++ b/app/Service/UserServiceInterface.php
@@ -0,0 +1,29 @@
+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/app/TaskWorker/SSDBTask.php b/app/TaskWorker/SSDBTask.php
new file mode 100644
index 0000000..fd4d451
--- /dev/null
+++ b/app/TaskWorker/SSDBTask.php
@@ -0,0 +1,46 @@
+client()->__call($method,$args);
+
+ return $result;
+ }
+
+ public function execWithoutTask($method,...$args)
+ {
+ $result = $this->client()->__call($method,$args);
+
+ return $result;
+ }
+
+ protected function client(){
+ if ($this->ss instanceof SimpleSSDB) {
+ return $this->ss;
+ }
+
+ $this->ss = new SimpleSSDB(env('SSDB_HOST'), env('SSDB_PORT'));
+ $this->ss->auth(env('SSDB_AUTH'));
+
+ return $this->ss;
+
+ }
+}
+
diff --git a/composer.json b/composer.json
index 1ac7889..284593c 100644
--- a/composer.json
+++ b/composer.json
@@ -19,7 +19,7 @@
"hyperf/config": "~2.0.0",
"hyperf/db-connection": "~2.0.0",
"hyperf/framework": "~2.0.0",
- "hyperf/guzzle": "~2.0.0",
+ "hyperf/guzzle": "^2.0",
"hyperf/http-server": "~2.0.0",
"hyperf/logger": "~2.0.0",
"hyperf/memory": "~2.0.0",
@@ -27,8 +27,16 @@
"hyperf/redis": "~2.0.0",
"hyperf/constants": "~2.0.0",
"hyperf/model-cache": "~2.0.0",
- "hyperf/validation": "^2.0",
- "hyperf/paginator": "^2.0"
+ "hyperf/filesystem": "^2.0",
+ "xxtime/flysystem-aliyun-oss": "^1.5",
+ "hyperf/validation": "^2.0",
+ "hyperf/task": "^2.0",
+ "hyperf/paginator": "^2.0",
+ "hyperf/amqp": "^2.0",
+ "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/amqp.php b/config/autoload/amqp.php
new file mode 100644
index 0000000..e14780a
--- /dev/null
+++ b/config/autoload/amqp.php
@@ -0,0 +1,33 @@
+ [
+ 'host' => env('RQM_HOST', 'localhost'),
+ 'port' => 5672,
+ 'user' => env('RQM_USER','guest'),
+ 'password' => env('RQM_PASSWORD','guest'),
+ 'vhost' => '/',
+ 'concurrent' => [
+ 'limit' => 1,
+ ],
+ 'pool' => [
+ 'min_connections' => 1,
+ 'max_connections' => 10,
+ 'connect_timeout' => 10.0,
+ 'wait_timeout' => 3.0,
+ 'heartbeat' => -1,
+ ],
+ 'params' => [
+ 'insist' => false,
+ 'login_method' => 'AMQPLAIN',
+ 'login_response' => null,
+ 'locale' => 'en_US',
+ 'connection_timeout' => 3.0,
+ 'read_write_timeout' => 6.0,
+ 'context' => null,
+ 'keepalive' => false,
+ 'heartbeat' => 3,
+ 'close_on_destruct' => false,
+ ],
+ ]
+];
diff --git a/config/autoload/dependencies.php b/config/autoload/dependencies.php
index 8b80220..9887862 100644
--- a/config/autoload/dependencies.php
+++ b/config/autoload/dependencies.php
@@ -9,7 +9,22 @@ 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,
+ \App\Service\ParamsTokenServiceInterface::class => \App\Service\ParamsTokenSsdbService::class,
+ \App\Service\AdServiceInterface::class => \App\Service\AdService::class,
+ \App\Commons\Log::class => \App\Commons\Log::class,
+ \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,
+ \App\Service\UserServiceInterface::class => \App\Service\UserService::class,
\App\Service\OrderListServiceInterface::class => \App\Service\OrderListService::class,
];
diff --git a/config/autoload/exceptions.php b/config/autoload/exceptions.php
index cca06d6..8d97cf6 100644
--- a/config/autoload/exceptions.php
+++ b/config/autoload/exceptions.php
@@ -14,7 +14,9 @@ return [
'http' => [
Hyperf\HttpServer\Exception\Handler\HttpExceptionHandler::class,
App\Exception\Handler\AppExceptionHandler::class,
- \App\Exception\Handler\ValidationExceptionHandler::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/middlewares.php b/config/autoload/middlewares.php
index 5269173..4841754 100644
--- a/config/autoload/middlewares.php
+++ b/config/autoload/middlewares.php
@@ -11,7 +11,7 @@ declare(strict_types=1);
*/
return [
'http' => [
- \App\Middleware\Auth\ApiMiddleware::class,
+ \App\Middleware\CorsMiddleware::class,
\Hyperf\Validation\Middleware\ValidationMiddleware::class,
],
];
diff --git a/config/autoload/server.php b/config/autoload/server.php
index e0e6cd8..f44b083 100644
--- a/config/autoload/server.php
+++ b/config/autoload/server.php
@@ -36,16 +36,13 @@ return [
'max_request' => 100000,
'socket_buffer_size' => 2 * 1024 * 1024,
'buffer_output_size' => 2 * 1024 * 1024,
- // Task Worker 数量,根据您的服务器配置而配置适当的数量
'task_worker_num' => 8,
- // 因为 `Task` 主要处理无法协程化的方法,所以这里推荐设为 `false`,避免协程下出现数据混淆的情况
'task_enable_coroutine' => false,
],
'callbacks' => [
SwooleEvent::ON_WORKER_START => [Hyperf\Framework\Bootstrap\WorkerStartCallback::class, 'onWorkerStart'],
SwooleEvent::ON_PIPE_MESSAGE => [Hyperf\Framework\Bootstrap\PipeMessageCallback::class, 'onPipeMessage'],
SwooleEvent::ON_WORKER_EXIT => [Hyperf\Framework\Bootstrap\WorkerExitCallback::class, 'onWorkerExit'],
- // Task callbacks
SwooleEvent::ON_TASK => [Hyperf\Framework\Bootstrap\TaskCallback::class, 'onTask'],
SwooleEvent::ON_FINISH => [Hyperf\Framework\Bootstrap\FinishCallback::class, 'onFinish'],
],
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 5032e31..d3b1568 100644
--- a/config/routes.php
+++ b/config/routes.php
@@ -23,6 +23,43 @@ Router::addGroup('/v1/',function (){
Router::post('ServiceEvaluate/isPersonnel', 'App\Controller\ServiceEvaluateController@isPersonnel');
Router::post('ServiceEvaluate/getPersonnelInfo', 'App\Controller\ServiceEvaluateController@getPersonnelInfo');
Router::post('ServiceEvaluate/getEvaluateList', 'App\Controller\ServiceEvaluateController@getEvaluateList');
+ Router::post('Attachment/uploadImage', 'App\Controller\AttachmentController@uploadImage');
+ Router::post('Attachment/uploadImageByBase64', 'App\Controller\AttachmentController@uploadImageByBase64');
+ Router::post('Attachment/upload', 'App\Controller\AttachmentController@upload');
+ Router::post('ParamsToken/generate', 'App\Controller\ParamsTokenController@generate');
+ Router::post('ParamsToken/analyze', 'App\Controller\ParamsTokenController@analyze');
+ Router::post('Ad/banners', 'App\Controller\AdController@banners');
+ Router::post('CouponRebate/isCouponRebate', 'App\Controller\CouponRebateController@isCouponRebate');
+ Router::post('CouponRebate/userReceiveCoupon', 'App\Controller\CouponRebateController@userReceiveCoupon');
+ Router::post('CouponRebate/getActiveInfo', 'App\Controller\CouponRebateController@getActiveInfo');
+ 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');
+
Router::post('OrderList/storeOrderList', 'App\Controller\OrderListController@storeOrderList');
Router::post('OrderList/userOrderList', 'App\Controller\OrderListController@userOrderList');
+},['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