diff --git a/README.md b/README.md index 6a4f9b7..8970111 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,13 @@ ## 更新日志 +### v1.1.40 +``` +新增:微信小程序直播功能 +优化:商品评价过滤无效内容 + +注:本次更新须重新发布小程序 +``` ### v1.1.39 ``` @@ -27,7 +34,6 @@ 修复:小程序端订单页未支付提示 注:本次更新须重新发布小程序 - ``` ### v1.1.38 diff --git a/doc/database/install.sql b/doc/database/install.sql index c978df9..1d2fad6 100644 --- a/doc/database/install.sql +++ b/doc/database/install.sql @@ -4716,6 +4716,12 @@ INSERT INTO `yoshop_store_access` VALUES ('10438', '积分管理', 'market.point INSERT INTO `yoshop_store_access` VALUES ('11003', '数据统计', 'statistics.data/index', '0', '138', '1572507520', '1572507520'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10462', '小程序直播', 'apps.live', '10074', '125', '1585120375', '1585120375'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10463', '直播间管理', 'apps.live.room/index', '10462', '100', '1585120404', '1585120404'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10464', '同步刷新', 'apps.live.room/refresh', '10463', '100', '1585120404', '1585120404'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10465', '设置置顶状态', 'apps.live.room/refresh', '10463', '100', '1585120404', '1585120404'); + + # 商家用户角色表 diff --git a/doc/database/upgrade/v1.1.40.sql b/doc/database/upgrade/v1.1.40.sql new file mode 100644 index 0000000..f9716da --- /dev/null +++ b/doc/database/upgrade/v1.1.40.sql @@ -0,0 +1,28 @@ + + +CREATE TABLE `yoshop_wxapp_live_room` ( + `room_id` int(11) unsigned NOT NULL COMMENT '直播间id', + `room_name` varchar(200) NOT NULL DEFAULT '' COMMENT '直播间名称', + `cover_img` varchar(255) DEFAULT '' COMMENT '分享卡片封面', + `share_img` varchar(255) DEFAULT '' COMMENT '直播间背景墙封面', + `anchor_name` varchar(30) NOT NULL DEFAULT '' COMMENT '主播昵称', + `start_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '开播时间', + `end_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '结束时间', + `live_status` tinyint(3) unsigned NOT NULL DEFAULT '102' COMMENT '直播状态(101: 直播中, 102: 未开始, 103: 已结束, 104: 禁播, 105: 暂停中, 106: 异常, 107: 已过期)', + `is_top` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '置顶状态(0未置顶 1已置顶)', + `is_delete` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '软删除(0未删除 1已删除)', + `wxapp_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '小程序id', + `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + PRIMARY KEY (`room_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='微信小程序直播间记录表'; + + + +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10462', '小程序直播', 'apps.live', '10074', '125', '1585120375', '1585120375'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10463', '直播间管理', 'apps.live.room/index', '10462', '100', '1585120404', '1585120404'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10464', '同步刷新', 'apps.live.room/refresh', '10463', '100', '1585120404', '1585120404'); +INSERT INTO `yoshop_store_access` (`access_id`, `name`, `url`, `parent_id`, `sort`, `create_time`, `update_time`) VALUES ('10465', '设置置顶状态', 'apps.live.room/refresh', '10463', '100', '1585120404', '1585120404'); + + + diff --git a/source/application/api/controller/live/Room.php b/source/application/api/controller/live/Room.php new file mode 100644 index 0000000..cf65998 --- /dev/null +++ b/source/application/api/controller/live/Room.php @@ -0,0 +1,26 @@ +getList(); + return $this->renderSuccess(compact('list')); + } +} \ No newline at end of file diff --git a/source/application/api/controller/user/Comment.php b/source/application/api/controller/user/Comment.php index 9cb0165..1fb21f6 100644 --- a/source/application/api/controller/user/Comment.php +++ b/source/application/api/controller/user/Comment.php @@ -41,8 +41,8 @@ class Comment extends Controller } // 提交商品评价 if ($this->request->isPost()) { - $formData = $this->request->post('formData', '', null); - if ($model->addForOrder($order, $goodsList, $formData)) { + $post = $this->request->post('formData'); + if ($model->addForOrder($order, $goodsList, $post)) { return $this->renderSuccess([], '评价发表成功'); } return $this->renderError($model->getError() ?: '评价发表失败'); diff --git a/source/application/api/model/Comment.php b/source/application/api/model/Comment.php index 398b7f3..ec82364 100644 --- a/source/application/api/model/Comment.php +++ b/source/application/api/model/Comment.php @@ -4,7 +4,7 @@ namespace app\api\model; use app\common\exception\BaseException; use app\common\model\Comment as CommentModel; -use think\Db; +use app\common\library\helper; /** * 商品评价模型 @@ -107,14 +107,14 @@ class Comment extends CommentModel * 根据已完成订单商品 添加评价 * @param Order $order * @param \think\Collection|OrderGoods $goodsList - * @param $formJsonData + * @param $post * @return boolean * @throws \Exception */ - public function addForOrder($order, $goodsList, $formJsonData) + public function addForOrder($order, $goodsList, $post) { // 生成 formData - $formData = $this->formatFormData($formJsonData); + $formData = $this->formatFormData($post); // 生成评价数据 $data = $this->createCommentData($order['user_id'], $order['order_id'], $goodsList, $formData); if (empty($data)) { @@ -173,6 +173,7 @@ class Comment extends CommentModel throw new BaseException(['msg' => '提交的数据不合法']); } $item = $formData[$goods['order_goods_id']]; + $item['content'] = trim($item['content']); !empty($item['content']) && $data[$goods['order_goods_id']] = [ 'score' => $item['score'], 'content' => $item['content'], @@ -191,12 +192,13 @@ class Comment extends CommentModel /** * 格式化 formData - * @param string $formJsonData + * @param string $post * @return array */ - private function formatFormData($formJsonData) + private function formatFormData($post) { - return array_column(json_decode($formJsonData, true), null, 'order_goods_id'); + $formJsonData = htmlspecialchars_decode($post); + return helper::arrayColumn2Key(helper::jsonDecode($formJsonData), 'order_goods_id'); } /** diff --git a/source/application/api/model/wxapp/LiveRoom.php b/source/application/api/model/wxapp/LiveRoom.php new file mode 100644 index 0000000..e0f30c0 --- /dev/null +++ b/source/application/api/model/wxapp/LiveRoom.php @@ -0,0 +1,77 @@ +where('live_status', '<>', 107); // 已过期的不显示 + $list = $this->where('is_delete', '=', 0) + ->order([ + 'is_top' => 'desc', + 'live_status' => 'asc', + 'create_time' => 'desc' + ])->paginate(15, false, [ + 'query' => \request()->request() + ]); + // 整理api数据 + foreach ($list as &$item) { + $item['live_status_text_1'] = LiveStatusEnum::data()[$item['live_status']]['name']; + $item['live_status_text_2'] = $item['live_status_text_1']; + $item['live_status'] == 101 && $item['live_status_text_1'] = '正在直播中'; + $item['live_status'] == 102 && $item['live_status_text_1'] = $this->semanticStartTime($item->getData('start_time')) . ' 开播'; + } + return $list; + } + + /** + * 语义化开播时间 + * @param $startTime + * @return string + */ + private function semanticStartTime($startTime) + { + // 转换为 YYYYMMDD 格式 + $startDate = date('Ymd', $startTime); + // 获取今天的 YYYY-MM-DD 格式 + $todyDate = date('Ymd'); + // 获取明天的 YYYY-MM-DD 格式 + $tomorrowDate = date('Ymd', strtotime('+1 day')); + // 使用IF当作字符串判断是否相等 + if ($startDate == $todyDate) { + return date('今天H:i', $startTime); + } elseif ($startDate == $tomorrowDate) { + return date('明天H:i', $startTime); + } + // 常规日期格式 + return date('m/d H:i', $startTime); + } + +} \ No newline at end of file diff --git a/source/application/common/enum/live/LiveStatus.php b/source/application/common/enum/live/LiveStatus.php new file mode 100644 index 0000000..77feb58 --- /dev/null +++ b/source/application/common/enum/live/LiveStatus.php @@ -0,0 +1,52 @@ + [ + 'name' => '直播中', + 'value' => 101, + ], + 102 => [ + 'name' => '未开始', + 'value' => 102, + ], + 103 => [ + 'name' => '已结束', + 'value' => 103, + ], + 104 => [ + 'name' => '禁播', + 'value' => 104, + ], + 105 => [ + 'name' => '暂停中', + 'value' => 105, + ], + 106 => [ + 'name' => '异常', + 'value' => 106, + ], + 107 => [ + 'name' => '已过期', + 'value' => 107, + ], + ]; + } + +} \ No newline at end of file diff --git a/source/application/common/library/wechat/WxBase.php b/source/application/common/library/wechat/WxBase.php index d1e18ad..7750d6a 100644 --- a/source/application/common/library/wechat/WxBase.php +++ b/source/application/common/library/wechat/WxBase.php @@ -56,8 +56,8 @@ class WxBase // 请求API获取 access_token $url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={$this->appId}&secret={$this->appSecret}"; $result = $this->get($url); - $data = $this->jsonDecode($result); - if (array_key_exists('errcode', $data)) { + $response = $this->jsonDecode($result); + if (array_key_exists('errcode', $response)) { throw new BaseException(['msg' => "access_token获取失败,错误信息:{$result}"]); } // 记录日志 @@ -68,7 +68,7 @@ class WxBase 'result' => $result ]); // 写入缓存 - Cache::set($cacheKey, $data['access_token'], 6000); // 7000 + Cache::set($cacheKey, $response['access_token'], 6000); // 7000 } return Cache::get($cacheKey); } diff --git a/source/application/common/library/wechat/live/Room.php b/source/application/common/library/wechat/live/Room.php new file mode 100644 index 0000000..8eb65ec --- /dev/null +++ b/source/application/common/library/wechat/live/Room.php @@ -0,0 +1,43 @@ +getAccessToken(); + $apiUrl = "http://api.weixin.qq.com/wxa/business/getliveinfo?access_token={$accessToken}"; + // 请求参数 + $params = $this->jsonEncode(['start' => 0, 'limit' => 100]); + // 执行请求 + $result = $this->post($apiUrl, $params); + // 记录日志 + $this->doLogs(['describe' => '微信小程序直播-获取直播房间列表接口', 'url' => $apiUrl, 'params' => $params, 'result' => $result]); + // 返回结果 + $response = $this->jsonDecode($result); + if (!isset($response['errcode'])) { + $this->error = 'not found errcode'; + return false; + } + if ($response['errcode'] != 0) { + $this->error = $response['errmsg']; + return false; + } + return $response; + } + +} \ No newline at end of file diff --git a/source/application/common/model/Wxapp.php b/source/application/common/model/Wxapp.php index 864188a..800f85e 100644 --- a/source/application/common/model/Wxapp.php +++ b/source/application/common/model/Wxapp.php @@ -31,26 +31,28 @@ class Wxapp extends BaseModel */ public static function detail($wxapp_id = null) { - return self::get($wxapp_id ?: []); + return static::get($wxapp_id ?: []); } /** * 从缓存中获取小程序信息 - * @param null $wxapp_id - * @return mixed|null|static + * @param int|null $wxappId 小程序id + * @return array $data * @throws BaseException + * @throws \think\Exception * @throws \think\exception\DbException */ - public static function getWxappCache($wxapp_id = null) + public static function getWxappCache($wxappId = null) { - if (is_null($wxapp_id)) { - $self = new static(); - $wxapp_id = $self::$wxapp_id; - } - if (!$data = Cache::get('wxapp_' . $wxapp_id)) { - $data = self::detail($wxapp_id); - if (empty($data)) throw new BaseException(['msg' => '未找到当前小程序信息']); - Cache::tag('cache')->set('wxapp_' . $wxapp_id, $data); + // 小程序id + is_null($wxappId) && $wxappId = static::$wxapp_id; + if (!$data = Cache::get("wxapp_{$wxappId}")) { + // 获取小程序详情, 解除hidden属性 + $detail = self::detail($wxappId)->hidden([], true); + if (empty($detail)) throw new BaseException(['msg' => '未找到当前小程序信息']); + // 写入缓存 + $data = $detail->toArray(); + Cache::tag('cache')->set("wxapp_{$wxappId}", $data); } return $data; } diff --git a/source/application/common/model/wxapp/LiveRoom.php b/source/application/common/model/wxapp/LiveRoom.php new file mode 100644 index 0000000..677e3d8 --- /dev/null +++ b/source/application/common/model/wxapp/LiveRoom.php @@ -0,0 +1,47 @@ +getList($search); + return $this->fetch('index', compact('list')); + } + + /** + * 同步刷新直播间列表 + * @return array|bool + * @throws \app\common\exception\BaseException + * @throws \think\exception\DbException + */ + public function refresh() + { + $model = new LiveRoomModel; + if ($model->refreshLiveList()) { + return $this->renderSuccess('同步成功'); + } + return $this->renderError($model->getError() ?: '同步失败'); + } + + /** + * 修改直播间置顶状态 + * @param $room_id + * @param $is_top + * @return array|bool + * @throws \think\exception\DbException + */ + public function settop($room_id, $is_top) + { + // 商品详情 + $model = LiveRoomModel::detail($room_id); + if (!$model->setIsTop($is_top)) { + return $this->renderError('操作失败'); + } + return $this->renderSuccess('操作成功'); + } + +} \ No newline at end of file diff --git a/source/application/store/extra/menus.php b/source/application/store/extra/menus.php index a1af21e..5767a97 100644 --- a/source/application/store/extra/menus.php +++ b/source/application/store/extra/menus.php @@ -544,7 +544,7 @@ return [ ], [ 'name' => '好物圈', - 'index' => 'apps.wow.order/index', + 'index' => 'apps.wow.shoping/index', 'submenu' => [ [ 'name' => '商品收藏', @@ -560,6 +560,16 @@ return [ ] ] ], + [ + 'name' => '小程序直播', + 'index' => 'apps.live.room/index', + 'submenu' => [ + [ + 'name' => '直播间管理', + 'index' => 'apps.live.room/index', + ], + ] + ], ] ], 'setting' => [ diff --git a/source/application/store/model/wxapp/LiveRoom.php b/source/application/store/model/wxapp/LiveRoom.php new file mode 100644 index 0000000..7a94579 --- /dev/null +++ b/source/application/store/model/wxapp/LiveRoom.php @@ -0,0 +1,201 @@ +where('room_name|anchor_name', 'like', "%{$search}%"); + return $this->where('is_delete', '=', 0) + ->order([ + 'is_top' => 'desc', + 'live_status' => 'asc', + 'create_time' => 'desc' + ]) + ->paginate(15, false, [ + 'query' => \request()->request() + ]); + } + + /** + * 设置直播间置顶状态 + * @param $isTop + * @return false|int + */ + public function setIsTop($isTop) + { + return $this->save(['is_top' => (int)$isTop]); + } + + /** + * 刷新直播间列表(同步微信api) + * 每次拉取上限100条数据 + * @throws BaseException + * @throws \think\exception\DbException + * @throws \Exception + */ + public function refreshLiveList() + { + // 获取微信api最新直播间列表信息 + $originRoomList = $this->getOriginRoomList(); + // 获取微信直播间的房间id集 + $originRoomIds = $this->getOriginRoomIds($originRoomList); + // 已存储的所有房间id集 + $localRoomIds = $this->getLocalRoomIds(); + // 同步新增直播间 + $this->refreshLiveNew($localRoomIds, $originRoomIds, $originRoomList); + // 同步删除直播间 + $this->refreshLiveRemove($localRoomIds, $originRoomIds); + // 同步更新直播间 + $this->refreshLiveUpdate($localRoomIds, $originRoomIds, $originRoomList); + return true; + } + + /** + * 获取微信api最新直播间列表信息 + * @return array + * @throws BaseException + * @throws \think\Exception + * @throws \think\exception\DbException + */ + private function getOriginRoomList() + { + // 小程序配置信息 + $wxConfig = WxappModel::getWxappCache(); + // 请求api数据 + $LiveRoomApi = new LiveRoomApi($wxConfig['app_id'], $wxConfig['app_secret']); + $response = $LiveRoomApi->getLiveRoomList(); + if ($response === false) { + throw new BaseException(['msg' => '直播房间列表api请求失败:' . $LiveRoomApi->getError()]); + } + // 格式化返回的列表数据 + $originRoomList = []; + foreach ($response['room_info'] as $item) { + $originRoomList[$item['roomid']] = $item; + } + return $originRoomList; + } + + /** + * 获取微信直播间的房间id集 + * @param $originRoomList + * @return array + */ + private function getOriginRoomIds($originRoomList) + { + $originRoomIds = []; + foreach ($originRoomList as $item) { + $originRoomIds[] = $item['roomid']; + } + return $originRoomIds; + } + + /** + * 获取数据库中已存在的roomid + * @return array + */ + private function getLocalRoomIds() + { + return $this->where('is_delete', '=', 0)->column('room_id'); + } + + /** + * 同步新增直播间 + * @param array $localRoomIds 本地直播间id集 + * @param array $originRoomIds 最新直播间id集 + * @param array $originRoomList 最新直播间列表 + * @return array|bool|false + * @throws \Exception + */ + private function refreshLiveNew($localRoomIds, $originRoomIds, $originRoomList) + { + // 需要新增的直播间ID + $newLiveRoomIds = array_values(array_diff($originRoomIds, $localRoomIds)); + if (empty($newLiveRoomIds)) return true; + // 整理新增数据 + $saveData = []; + foreach ($newLiveRoomIds as $roomId) { + $item = $originRoomList[$roomId]; + $saveData[] = [ + 'room_id' => $roomId, + 'room_name' => $item['name'], + 'cover_img' => $item['cover_img'], + 'share_img' => $item['share_img'], + 'anchor_name' => $item['anchor_name'], + 'start_time' => $item['start_time'], + 'end_time' => $item['end_time'], + 'live_status' => $item['live_status'], + 'wxapp_id' => self::$wxapp_id, + ]; + } + // 批量新增直播间 + return $this->isUpdate(false)->saveAll($saveData, false); + } + + /** + * 同步更新直播间 + * @param array $localRoomIds 本地直播间id集 + * @param array $originRoomIds 最新直播间id集 + * @param array $originRoomList 最新直播间列表 + * @return array|bool|false + * @throws \Exception + */ + private function refreshLiveUpdate($localRoomIds, $originRoomIds, $originRoomList) + { + // 需要新增的直播间ID + $updatedLiveRoomIds = array_values(array_intersect($originRoomIds, $localRoomIds)); + if (empty($updatedLiveRoomIds)) return true; + // 整理新增数据 + $saveData = []; + foreach ($updatedLiveRoomIds as $roomId) { + $item = $originRoomList[$roomId]; + $saveData[] = [ + 'room_id' => $roomId, + 'room_name' => $item['name'], + 'cover_img' => $item['cover_img'], + 'share_img' => $item['share_img'], + 'anchor_name' => $item['anchor_name'], + 'start_time' => $item['start_time'], + 'end_time' => $item['end_time'], + 'live_status' => $item['live_status'], + 'wxapp_id' => self::$wxapp_id, + ]; + } + // 批量新增直播间 + return $this->isUpdate(true)->saveAll($saveData); + } + + /** + * 同步删除直播间 + * @param array $localRoomIds 本地直播间id集 + * @param array $originRoomIds 最新直播间id集 + * @return array|bool|false + * @throws \Exception + */ + private function refreshLiveRemove($localRoomIds, $originRoomIds) + { + // 需要新增的直播间ID + $removedLiveRoomIds = array_values(array_diff($localRoomIds, $originRoomIds)); + if (empty($removedLiveRoomIds)) return true; + // 批量删除直播间 + return self::destroy($removedLiveRoomIds); + } + +} \ No newline at end of file diff --git a/source/application/store/view/apps/live/room/index.php b/source/application/store/view/apps/live/room/index.php new file mode 100644 index 0000000..82331cf --- /dev/null +++ b/source/application/store/view/apps/live/room/index.php @@ -0,0 +1,151 @@ + +