Merge remote-tracking branch 'origin/1.0' into 1.0
| | |
| | | package com.xiaopeng.account.controller; |
| | | |
| | | |
| | | import cn.hutool.core.util.StrUtil; |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.xiaopeng.account.model.dto.ReceiveCouponDto; |
| | | import com.xiaopeng.account.model.dto.SendCouponDto; |
| | |
| | | import com.xiaopeng.account.service.IXpAppUserCouponService; |
| | | import com.xiaopeng.api.dto.account.GetUserCouponDto; |
| | | import com.xiaopeng.api.dto.account.NavigationCouponPickerRequest; |
| | | import com.xiaopeng.api.vo.account.NavigationCouponPickerVo; |
| | | import com.xiaopeng.api.dto.account.UseUserCouponDto; |
| | | import com.xiaopeng.api.feignClient.account.UserResourcesClient; |
| | | import com.xiaopeng.api.feignClient.operation.XpActicityClient; |
| | | import com.xiaopeng.api.feignClient.operation.XpCouponClient; |
| | | import com.xiaopeng.api.feignClient.operation.XpOpenCityClient; |
| | | import com.xiaopeng.api.model.account.XpAppUserCoupon; |
| | | import com.xiaopeng.api.model.operation.XpCoupon; |
| | | import com.xiaopeng.api.query.account.XpAppUserCouponPageListQuery; |
| | | import com.xiaopeng.api.vo.account.NavigationCouponPickerVo; |
| | | import com.xiaopeng.api.vo.account.UserCouponVo; |
| | | import com.xiaopeng.common.core.domain.R; |
| | | import com.xiaopeng.common.core.web.page.PageInfo; |
| | | import com.xiaopeng.unifiedverification.interceptor.XpClientCheck; |
| | | import io.swagger.annotations.ApiOperation; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import javax.annotation.Resource; |
| | | import javax.servlet.http.HttpServletRequest; |
| | | import javax.validation.Valid; |
| | | import java.math.BigDecimal; |
| | |
| | | private final UserResourcesClient userResourcesClient; |
| | | private final XpCouponClient xpCouponClient; |
| | | |
| | | @Resource |
| | | private XpActicityClient xpActicityClient; |
| | | |
| | | |
| | | public XpAppUserCouponController(IXpAppUserCouponService xpAppUserCouponService, XpOpenCityClient xpOpenCityClient, UserResourcesClient userResourcesClient, XpCouponClient xpCouponClient) { |
| | | this.xpAppUserCouponService = xpAppUserCouponService; |
| | |
| | | this.xpCouponClient = xpCouponClient; |
| | | } |
| | | |
| | | @PostMapping("/v1/api/appUserCoupon/pageList") |
| | | @ApiOperation(value = "用户优惠券列表",tags = {"管理后台-用户优惠券列表"},response = XpAppUserCouponListVo.class) |
| | | public R<PageInfo<XpAppUserCouponListVo>> pageList(@RequestBody XpAppUserCouponPageListQuery query) { |
| | | |
| | | if(StrUtil.isNotBlank(query.getActicityName()) ){ |
| | | List<String> acticityIds = xpActicityClient.findListToIds(query.getActicityName()).getData(); |
| | | query.setActicityIds(acticityIds); |
| | | } |
| | | |
| | | PageInfo<XpAppUserCouponListVo> pageList = xpAppUserCouponService.pageList(query); |
| | | return R.ok(pageList); |
| | | } |
| | | |
| | | @PostMapping("/logan/inner/appUserCoupon/getUserCoupon") |
| | | public R<UserCouponVo> getUserCoupon(@RequestBody GetUserCouponDto dto) { |
| | |
| | | import com.xiaopeng.account.model.query.XpUserCouponListQuery; |
| | | import com.xiaopeng.account.model.vo.XpAppUserCouponListVo; |
| | | import com.xiaopeng.api.model.account.XpAppUserCoupon; |
| | | import com.xiaopeng.api.query.account.XpAppUserCouponPageListQuery; |
| | | import com.xiaopeng.common.core.web.page.PageInfo; |
| | | import org.apache.ibatis.annotations.Param; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * <p> |
| | |
| | | |
| | | Page<XpAppUserCouponListVo> userCouponList(@Param("page") Page<XpAppUserCouponListVo> page, @Param("query") XpUserCouponListQuery query, @Param("uid") Long uid); |
| | | |
| | | |
| | | List<XpAppUserCouponListVo> pageList(@Param("pageInfo") PageInfo<XpAppUserCouponListVo> pageInfo, @Param("query") XpAppUserCouponPageListQuery query); |
| | | |
| | | } |
| | |
| | | package com.xiaopeng.account.model.vo; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.IdType; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.fasterxml.jackson.annotation.JsonFormat; |
| | | import io.swagger.annotations.ApiModel; |
| | | import io.swagger.annotations.ApiModelProperty; |
| | |
| | | @ApiModel("用户优惠券列表Vo") |
| | | public class XpAppUserCouponListVo { |
| | | |
| | | @TableId(value = "id", type = IdType.ASSIGN_ID) |
| | | private Long id; |
| | | |
| | | @ApiModelProperty(value = "Id") |
| | | private String couponId; |
| | | |
| | | @ApiModelProperty(value = "用户id 根据手机号获取") |
| | | private Long appUserId; |
| | | |
| | | @ApiModelProperty(value = "优惠券名称") |
| | | private String title; |
| | |
| | | |
| | | @ApiModelProperty(value = "优惠券说明") |
| | | private String couponDesc; |
| | | |
| | | @ApiModelProperty(value = "活动名称") |
| | | private String acticityName; |
| | | |
| | | } |
| | |
| | | import com.xiaopeng.account.model.dto.ReceiveCouponDto; |
| | | import com.xiaopeng.account.model.dto.SendCouponDto; |
| | | import com.xiaopeng.account.model.query.XpUserCouponListQuery; |
| | | import com.xiaopeng.account.model.vo.XpAppUserCouponListVo; |
| | | import com.xiaopeng.account.model.vo.XpAppUserCouponTotalVo; |
| | | import com.xiaopeng.account.model.vo.XpAppUserCouponVo; |
| | | import com.xiaopeng.api.dto.account.NavigationCouponPickerRequest; |
| | | import com.xiaopeng.api.model.account.XpAppUserCoupon; |
| | | import com.xiaopeng.api.query.account.XpAppUserCouponPageListQuery; |
| | | import com.xiaopeng.api.vo.account.NavigationCouponPickerVo; |
| | | import com.xiaopeng.common.core.domain.R; |
| | | import com.xiaopeng.common.core.web.page.PageInfo; |
| | | |
| | | import java.util.List; |
| | | |
| | |
| | | */ |
| | | public interface IXpAppUserCouponService extends IService<XpAppUserCoupon> { |
| | | |
| | | PageInfo<XpAppUserCouponListVo> pageList(XpAppUserCouponPageListQuery query); |
| | | |
| | | /** |
| | | * 获取用户优惠券列表 |
| | | * @param query |
| | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
| | | import com.google.common.collect.Lists; |
| | | import com.xiaopeng.account.mapper.XpAppUserCouponMapper; |
| | | import com.xiaopeng.account.model.dto.ReceiveCouponDto; |
| | | import com.xiaopeng.account.model.dto.SendCouponDto; |
| | |
| | | import com.xiaopeng.api.dto.account.NavigationCouponPickerRequest; |
| | | import com.xiaopeng.api.dto.account.UcUserInfo; |
| | | import com.xiaopeng.api.enums.NavigationCouponBubbleType; |
| | | import com.xiaopeng.api.query.account.XpAppUserCouponPageListQuery; |
| | | import com.xiaopeng.api.vo.account.NavigationCouponItemVo; |
| | | import com.xiaopeng.api.vo.account.NavigationCouponPickerVo; |
| | | import com.xiaopeng.api.feignClient.account.UserResourcesClient; |
| | |
| | | import com.xiaopeng.api.model.operation.XpCoupon; |
| | | import com.xiaopeng.common.core.domain.R; |
| | | import com.xiaopeng.common.core.utils.DateUtils; |
| | | import com.xiaopeng.common.core.web.page.PageInfo; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.scheduling.annotation.Async; |
| | | import org.springframework.stereotype.Service; |
| | |
| | | private UserResourcesClient userResourcesClient; |
| | | |
| | | |
| | | @Override |
| | | public PageInfo<XpAppUserCouponListVo> pageList(XpAppUserCouponPageListQuery query) { |
| | | PageInfo<XpAppUserCouponListVo> pageInfo = new PageInfo<>(query.getPageNum(),query.getPageSize()); |
| | | List<XpAppUserCouponListVo> list = this.baseMapper.pageList(pageInfo,query); |
| | | pageInfo.setRecords(list); |
| | | return pageInfo; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 获取用户优惠券列表 |
| | | * |
| | |
| | | id, full_money, money, expiration_time, status, coupon_type, app_user_id, coupon_id, activity_id, activity_type, is_deleted, create_time, update_time, create_by, update_by |
| | | </sql> |
| | | |
| | | |
| | | <select id="pageList" resultType="com.xiaopeng.account.model.vo.XpAppUserCouponListVo"> |
| | | select a.* |
| | | from xp_app_user_coupon as a |
| | | where a.is_deleted = 0 and a.app_user_id = #{appUserId} |
| | | <if test="query.createTimeStart != null "> |
| | | a.create_time >= #{query.createTimeStart} |
| | | </if> |
| | | <if test="query.createTimeEnd != null "> |
| | | a.create_time <= #{query.createTimeEnd} |
| | | </if> |
| | | <if test="query.status != null"> |
| | | and a.status = #{query.status} |
| | | </if> |
| | | <if test="query.acticityIds != null"> |
| | | and a.activity_id in |
| | | <foreach item="item" collection="query.acticityIds" separator="," open="(" close=")" index=""> |
| | | #{item} |
| | | </foreach> |
| | | </if> |
| | | order by a.create_time desc |
| | | </select> |
| | | |
| | | <select id="userCouponList" resultType="com.xiaopeng.account.model.vo.XpAppUserCouponListVo"> |
| | | select t.*,t.status from ( |
| | |
| | | private String appId; |
| | | private String headImg; |
| | | private Long id; |
| | | private String mobile; |
| | | private String nickName; |
| | | private String mobile;//用户手机号 |
| | | private String nickName;//用户名称 |
| | | private List<String> orderList; |
| | | private Integer orderType; |
| | | private Integer state; |
| | | private Integer type; |
| | | private Long uid; |
| | | private Long uid; //用户id |
| | | private List<String> bindCars; // 空时为 null |
| | | private Integer musicVip; // 0-非音频会员,1-音频会员 |
| | | } |
| New file |
| | |
| | | package com.xiaopeng.api.factory.operation; |
| | | |
| | | import com.xiaopeng.api.feignClient.operation.XpActicityClient; |
| | | import com.xiaopeng.common.core.domain.R; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.cloud.openfeign.FallbackFactory; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | import java.util.List; |
| | | |
| | | @Slf4j |
| | | @Component |
| | | public class XpActicityClientFallbackFactory implements FallbackFactory<XpActicityClient> { |
| | | |
| | | @Override |
| | | public XpActicityClient create(Throwable cause) { |
| | | return new XpActicityClient() { |
| | | |
| | | @Override |
| | | public R<List<String>> findListToIds(String acticityName) { |
| | | return R.ok(); |
| | | } |
| | | }; |
| | | } |
| | | |
| | | } |
| New file |
| | |
| | | package com.xiaopeng.api.feignClient.operation; |
| | | |
| | | |
| | | import com.xiaopeng.api.factory.operation.XpCouponClientFallbackFactory; |
| | | import com.xiaopeng.common.core.domain.R; |
| | | import com.xiaopeng.common.core.dto.ServiceNameConstantsEnum; |
| | | import org.springframework.cloud.openfeign.FeignClient; |
| | | import org.springframework.web.bind.annotation.PostMapping; |
| | | import org.springframework.web.bind.annotation.RequestParam; |
| | | |
| | | import java.util.List; |
| | | |
| | | @FeignClient(contextId = "XpActicityClient", |
| | | value = ServiceNameConstantsEnum.XP_OPERATION_SERVICE, |
| | | fallbackFactory = XpCouponClientFallbackFactory.class) |
| | | public interface XpActicityClient { |
| | | |
| | | |
| | | |
| | | @PostMapping("/logan/inner/acticity/findListToIds") |
| | | R<List<String>> findListToIds(@RequestParam("acticityName") String acticityName); |
| | | } |
| New file |
| | |
| | | package com.xiaopeng.api.feignClient.vehicleConvoy; |
| | | |
| | | |
| | | import com.xiaopeng.api.dto.vehicleConvoy.BaseVehicleUserDto; |
| | | import com.xiaopeng.api.factory.vehicleConvoy.UserVehicleClientFallbackFactory; |
| | | import com.xiaopeng.api.vo.vehicleConvoy.BluetoothDeliverVo; |
| | | import com.xiaopeng.common.core.dto.ServiceNameConstantsEnum; |
| | | import org.springframework.cloud.openfeign.FeignClient; |
| | | import org.springframework.web.bind.annotation.PostMapping; |
| | | import org.springframework.web.bind.annotation.RequestBody; |
| | | |
| | | |
| | | /** |
| | | * 蓝牙解锁车门 |
| | | */ |
| | | // todo 需要修改蓝牙微服务调用名 |
| | | @FeignClient(contextId = "BluetoothUnlockClient", |
| | | value = ServiceNameConstantsEnum.XP_VEHICLE_USER_SERVICE, |
| | | fallbackFactory = UserVehicleClientFallbackFactory.class) |
| | | public interface BluetoothUnlockClient { |
| | | |
| | | |
| | | /** |
| | | * 下发凭证 |
| | | * @param vo |
| | | */ |
| | | @PostMapping("/logan/rt/credential/deliver") |
| | | BaseVehicleUserDto deliver(@RequestBody BluetoothDeliverVo vo); |
| | | |
| | | |
| | | /** |
| | | * 删除凭证 |
| | | * @param vo |
| | | */ |
| | | @PostMapping("/logan/rt/credential/delete") |
| | | BaseVehicleUserDto delete(@RequestBody BluetoothDeliverVo vo); |
| | | } |
| | |
| | | @Max(value = 99999, message = "最大不能超过99999") |
| | | private Integer maxOrderNum; |
| | | |
| | | @ApiModelProperty(value = "用户id 根据手机号获取") |
| | | private Long appUserId; |
| | | |
| | | public Boolean getStatus() { |
| | | if (LocalDate.now().isAfter(expireTime)) { |
| | | return false; |
| | | if(expireTime!= null){ |
| | | if (LocalDate.now().isAfter(expireTime)) { |
| | | return false; |
| | | } |
| | | return status; |
| | | } |
| | | return status; |
| | | } |
| New file |
| | |
| | | package com.xiaopeng.api.query.account; |
| | | |
| | | import com.fasterxml.jackson.annotation.JsonFormat; |
| | | import com.xiaopeng.common.core.web.page.BasePage; |
| | | import io.swagger.annotations.ApiModelProperty; |
| | | import lombok.Data; |
| | | |
| | | import java.time.LocalDateTime; |
| | | import java.util.List; |
| | | |
| | | @Data |
| | | public class XpAppUserCouponPageListQuery extends BasePage { |
| | | |
| | | |
| | | @ApiModelProperty(value = "用户id 根据手机号获取") |
| | | private Long appUserId; |
| | | |
| | | |
| | | @ApiModelProperty(value = "状态 1-未使用 2-已使用 3-已过期 0待领取") |
| | | private Integer status; |
| | | |
| | | @ApiModelProperty(value = "活动名称") |
| | | private String acticityName; |
| | | |
| | | @ApiModelProperty(value = "领取时间开始") |
| | | @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8") |
| | | private LocalDateTime createTimeStart; |
| | | |
| | | @ApiModelProperty(value = "领取时间结束") |
| | | @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8") |
| | | private LocalDateTime createTimeEnd; |
| | | |
| | | private List<String> acticityIds; |
| | | |
| | | } |
| | |
| | | import org.springframework.data.mongodb.core.geo.GeoJsonPoint; |
| | | import org.springframework.data.mongodb.core.index.CompoundIndex; |
| | | import org.springframework.data.mongodb.core.index.CompoundIndexes; |
| | | import org.springframework.data.mongodb.core.index.GeoSpatialIndexType; |
| | | import org.springframework.data.mongodb.core.index.GeoSpatialIndexed; |
| | | import org.springframework.data.mongodb.core.mapping.Document; |
| | | |
| | | import java.io.Serializable; |
| | |
| | | /** |
| | | * 车辆实时位置信息 |
| | | */ |
| | | @GeoSpatialIndexed(type = GeoSpatialIndexType.GEO_2DSPHERE) |
| | | private GeoJsonPoint point; |
| | | /** |
| | | * 航向,单位:度,北零顺时针。范围:[0-360)。0 => 北, 90 => 东, 180 => 南, 270 => 西 |
| New file |
| | |
| | | package com.xiaopeng.api.vo.vehicleConvoy; |
| | | |
| | | |
| | | import lombok.Data; |
| | | |
| | | @Data |
| | | public class BluetoothDeliverVo { |
| | | /** |
| | | * VIM码 |
| | | */ |
| | | private String vim; |
| | | /** |
| | | * 乘客用户 ID |
| | | */ |
| | | private Long passengerUid; |
| | | /** |
| | | * 鉴权凭证 |
| | | */ |
| | | private String credential; |
| | | } |
| | |
| | | com.xiaopeng.api.factory.map.XpStationClientFallbackFactory |
| | | com.xiaopeng.api.factory.pushMessage.SubscribeMessageClientFallbackFactory |
| | | com.xiaopeng.api.factory.account.UserSubscribeMessageClientFallbackFactory |
| | | com.xiaopeng.api.factory.operation.XpTradingStrategyClientFallbackFactory |
| | | com.xiaopeng.api.factory.operation.XpTradingStrategyClientFallbackFactory |
| | | com.xiaopeng.api.factory.operation.XpActicityClientFallbackFactory |
| | |
| | | import com.google.protobuf.InvalidProtocolBufferException; |
| | | import com.google.protobuf.util.JsonFormat; |
| | | import com.xiaopeng.api.dto.automaticPilot.MqttPublishDto; |
| | | import com.xiaopeng.api.dto.automaticPilot.PushOrderDto; |
| | | import com.xiaopeng.api.dto.automaticPilot.VehicleEventDTO; |
| | | import com.xiaopeng.api.dto.map.HistoryPointDto; |
| | | import com.xiaopeng.api.dto.map.OrderRouteAndFareDto; |
| | | import com.xiaopeng.api.dto.operation.CruisePointDto; |
| | | import com.xiaopeng.api.dto.operation.LonLatDto; |
| | |
| | | import com.xiaopeng.common.redis.service.RedisService; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.redisson.api.RLock; |
| | | import org.redisson.api.RTopic; |
| | | import org.redisson.api.RedissonClient; |
| | | import org.springframework.beans.factory.annotation.Value; |
| | | import org.springframework.data.geo.Point; |
| | |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import javax.annotation.Resource; |
| | | import java.math.BigDecimal; |
| | | import java.util.*; |
| | | import java.util.concurrent.*; |
| | | import java.util.concurrent.LinkedBlockingQueue; |
| | | import java.util.concurrent.ThreadPoolExecutor; |
| | | import java.util.concurrent.TimeUnit; |
| | | |
| | | |
| | | /** |
| | |
| | | reassignOrderDTO.setUid(xpOrder.getAppUserId().toString()); |
| | | reassignOrderDTO.setOrderNo(xpOrder.getOrderNumber()); |
| | | reassignOrderDTO.setCount(10); |
| | | RealtimePose pose = systemRuntimeStatus.getPose(); |
| | | Wgs naviPose = pose.getNaviPose(); |
| | | Double latitude = naviPose.getLatitude(); |
| | | Double longitude = naviPose.getLongitude(); |
| | | reassignOrderDTO.setLongitude(longitude); |
| | | reassignOrderDTO.setLatitude(latitude); |
| | | // RealtimePose pose = systemRuntimeStatus.getPose(); |
| | | // Wgs naviPose = pose.getNaviPose(); |
| | | // Double latitude = naviPose.getLatitude(); |
| | | // Double longitude = naviPose.getLongitude(); |
| | | reassignOrderDTO.setLongitude(xpOrder.getStartLon()); |
| | | reassignOrderDTO.setLatitude(xpOrder.getStartLat()); |
| | | reassignOrderClient.reassignOrder(reassignOrderDTO); |
| | | } |
| | | } |
| | |
| | | buildPushOrderStatus(cmd, orderId, OrderStatusEnum.SEE_OFF_GUESTS, sequence); |
| | | xpOrderClient.updateOrderStatus(Long.parseLong(orderId), OrderStatusEnum.SEE_OFF_GUESTS.getCode(), true); |
| | | redisService.setCacheObject("ORDER_STATUS:" + orderId, OrderStatusEnum.SEE_OFF_GUESTS.getCode(), 24L, TimeUnit.HOURS); |
| | | // todo 判断当前订单的车辆是否有连环派单,然后判断是都还满足连环派单的条件,不满足则需要重新派单 |
| | | |
| | | // 判断当前订单的车辆是否有连环派单,然后判断是都还满足连环派单的条件,不满足则需要重新派单 |
| | | Long nextOrderId = redisService.getCacheObject(CacheConstants.nextOrder(orderId)); |
| | | if(null != nextOrderId){ |
| | | XpOrder nextOrder = xpOrderClient.queryOrderById(nextOrderId).getData(); |
| | | if(null != nextOrder && nextOrder.getStatus() == OrderStatusEnum.PICK_UP.getCode()){ |
| | | redisService.setCacheObject(CacheConstants.nextOrder(orderId),null); |
| | | ReassignOrderDTO reassignOrderDTO = new ReassignOrderDTO(); |
| | | reassignOrderDTO.setUid(xpOrder.getAppUserId().toString()); |
| | | reassignOrderDTO.setOrderNo(xpOrder.getOrderNumber()); |
| | | reassignOrderDTO.setCount(10); |
| | | reassignOrderDTO.setLongitude(xpOrder.getStartLon()); |
| | | reassignOrderDTO.setLatitude(xpOrder.getStartLon()); |
| | | reassignOrderClient.reassignOrder(reassignOrderDTO); |
| | | } |
| | | } |
| | | redisService.getCacheObject("ORDER_CONNECT_ORDER:" + orderId); |
| | | |
| | | }else{ |
| | | buildPushOrderStatus(cmd, orderId, OrderStatusEnum.CANCELED, sequence); |
| | |
| | | |
| | | import com.xiaopeng.api.dto.map.RouteAndFareDto; |
| | | import com.xiaopeng.api.dto.order.OrderChangeDto; |
| | | import com.xiaopeng.api.vo.automaticPilot.VehiclePosition; |
| | | import com.xiaopeng.api.vo.operation.RouteAndFareVo; |
| | | import com.xiaopeng.common.core.domain.R; |
| | | import com.xiaopeng.itinerary.service.ActiveOrderService; |
| | |
| | | import io.swagger.annotations.Api; |
| | | import io.swagger.annotations.ApiOperation; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.data.geo.Distance; |
| | | import org.springframework.data.geo.Metrics; |
| | | import org.springframework.data.geo.Point; |
| | | import org.springframework.data.mongodb.core.MongoTemplate; |
| | | import org.springframework.data.mongodb.core.query.Criteria; |
| | | import org.springframework.data.mongodb.core.query.Query; |
| | | import org.springframework.validation.annotation.Validated; |
| | | import org.springframework.web.bind.annotation.PostMapping; |
| | | import org.springframework.web.bind.annotation.RequestBody; |
| | | import org.springframework.web.bind.annotation.RequestMapping; |
| | | import org.springframework.web.bind.annotation.RestController; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import javax.annotation.Resource; |
| | | import javax.servlet.http.HttpServletRequest; |
| | |
| | | return R.ok(); |
| | | } |
| | | |
| | | /** |
| | | * 周围车辆点位查询 |
| | | */ |
| | | @ApiOperation(value = "周围车辆点位查询", notes = "", tags = {"小程序-周围车辆点位查询"}) |
| | | @GetMapping("/api/v1/mini-program/nearby/cars") |
| | | @XpClientCheck(checkToken = false, checkOtpToken = true) |
| | | public R<List<VehiclePosition>> carAround(@RequestParam double lon,@RequestParam double lat,@RequestParam double distanceMeters,HttpServletRequest request) { |
| | | |
| | | String uid = request.getHeader("XP-Uid"); |
| | | |
| | | Point point = new Point(lon, lat); |
| | | Distance distance = new Distance(distanceMeters / 1000.0, Metrics.KILOMETERS); |
| | | Criteria criteria = Criteria.where("point") |
| | | .nearSphere(point) |
| | | .maxDistance(distance.getNormalizedValue()); |
| | | Query query = new Query(criteria); |
| | | return R.ok(mongoTemplate.find(query, VehiclePosition.class)); |
| | | } |
| | | @Resource |
| | | private MongoTemplate mongoTemplate; |
| | | |
| | | |
| | | } |
| | |
| | | |
| | | |
| | | import com.alibaba.fastjson2.JSON; |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.xiaopeng.api.dto.operation.ReassignOrderDTO; |
| | | import com.xiaopeng.api.dto.order.OrderChangeDto; |
| | | import com.xiaopeng.api.feignClient.map.XpMapClient; |
| | | import com.xiaopeng.api.feignClient.operation.ReassignOrderClient; |
| | | import com.xiaopeng.api.feignClient.operation.XpPointClient; |
| | | import com.xiaopeng.api.feignClient.operation.XpSystemSettingClient; |
| | | import com.xiaopeng.api.feignClient.order.XpOrderClient; |
| | | import com.xiaopeng.api.model.operation.*; |
| | | import com.xiaopeng.api.model.operation.XpElectronicFence; |
| | | import com.xiaopeng.api.model.operation.XpElectronicFenceArea; |
| | | import com.xiaopeng.api.model.operation.XpPoint; |
| | | import com.xiaopeng.api.model.operation.XpSystemSetting; |
| | | import com.xiaopeng.api.model.order.XpOrder; |
| | | import com.xiaopeng.api.vo.operation.RouteOptionVo; |
| | | import com.xiaopeng.api.vo.operation.RouteVo; |
| | | import com.xiaopeng.common.core.constant.CacheConstants; |
| | | import com.xiaopeng.common.core.domain.R; |
| | | import com.xiaopeng.common.core.enums.OrderStatusEnum; |
| | | import com.xiaopeng.common.core.exception.ServiceException; |
| | | import com.xiaopeng.common.redis.service.RedisService; |
| | | import com.xiaopeng.itinerary.constant.I18nKeyConstant; |
| | |
| | | |
| | | import javax.annotation.Resource; |
| | | import java.time.LocalDateTime; |
| | | import java.util.*; |
| | | import java.util.StringJoiner; |
| | | import java.util.concurrent.TimeUnit; |
| | | |
| | | |
| | |
| | | xpOrder.setQueueName(key); |
| | | xpOrderClient.updateOrder(xpOrder); |
| | | |
| | | // todo 判断当前订单的车辆是否有连环派单,然后判断是都还满足连环派单的条件,不满足则需要重新派单 |
| | | |
| | | // 判断当前订单的车辆是否有连环派单,然后判断是都还满足连环派单的条件,不满足则需要重新派单 |
| | | Long nextOrderId = redisService.getCacheObject(CacheConstants.nextOrder(order.getId().toString())); |
| | | if(null != nextOrderId){ |
| | | XpOrder nextOrder = xpOrderClient.queryOrderById(nextOrderId).getData(); |
| | | if(null != nextOrder && nextOrder.getStatus() == OrderStatusEnum.PICK_UP.getCode()){ |
| | | redisService.setCacheObject(CacheConstants.nextOrder(order.getId().toString()),null); |
| | | ReassignOrderDTO reassignOrderDTO = new ReassignOrderDTO(); |
| | | reassignOrderDTO.setUid(xpOrder.getAppUserId().toString()); |
| | | reassignOrderDTO.setOrderNo(xpOrder.getOrderNumber()); |
| | | reassignOrderDTO.setCount(10); |
| | | reassignOrderDTO.setLongitude(xpOrder.getStartLon()); |
| | | reassignOrderDTO.setLatitude(xpOrder.getStartLon()); |
| | | reassignOrderClient.reassignOrder(reassignOrderDTO); |
| | | } |
| | | } |
| | | } |
| | | |
| | | @Resource |
| | | private ReassignOrderClient reassignOrderClient; |
| | | /** |
| | | * 获取距离和时间 |
| | | * @param uid |
| | |
| | | package com.xiaopeng.operation.controller; |
| | | |
| | | |
| | | import org.springframework.web.bind.annotation.RequestMapping; |
| | | import com.xiaopeng.common.core.domain.R; |
| | | import com.xiaopeng.operation.service.IXpActicityService; |
| | | import io.swagger.annotations.Api; |
| | | import io.swagger.annotations.ApiOperation; |
| | | import lombok.RequiredArgsConstructor; |
| | | import org.springframework.context.annotation.Lazy; |
| | | import org.springframework.web.bind.annotation.PostMapping; |
| | | import org.springframework.web.bind.annotation.RequestParam; |
| | | |
| | | import org.springframework.stereotype.Controller; |
| | | import javax.annotation.Resource; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * <p> |
| | |
| | | * @author mitao |
| | | * @since 2026-02-03 |
| | | */ |
| | | @Controller |
| | | @RequestMapping("/xp-acticity") |
| | | @Api(tags = "活动管理") |
| | | @RequiredArgsConstructor(onConstructor_ = @Lazy) |
| | | public class XpActicityController { |
| | | |
| | | @Resource |
| | | private IXpActicityService xpActicityService; |
| | | |
| | | |
| | | /** |
| | | * 获取所有白名单手机号 |
| | | * @return |
| | | */ |
| | | @PostMapping("/logan/inner/acticity/findListToIds") |
| | | @ApiOperation(value = "获取活动信息ids", tags = {"管理后台-获取活动信息ids"}) |
| | | public R<List<String>> findListToIds(@RequestParam String acticityName){ |
| | | if(acticityName == null){ |
| | | return R.ok(); |
| | | } |
| | | return R.ok(xpActicityService.findListToIds(acticityName)); |
| | | } |
| | | } |
| | |
| | | return R.ok(xpWhitelistService.getAllWhiteList()); |
| | | } |
| | | |
| | | |
| | | @ApiOperation(value = "白名单管理详情", tags = {"管理后台-白名单管理详情"}) |
| | | @GetMapping("/api/v1/whitelist/detail/{id}") |
| | | public R<XpWhitelist> detail(@PathVariable("id") Long id) { |
| | | return R.ok(xpWhitelistService.getById(id)); |
| | | } |
| | | |
| | | |
| | | @ApiOperation(value = "白名单管理分页列表", response = XpWhitelist.class) |
| | | @PostMapping("/api/v1/whitelist/pageList") |
| | | public R<PageInfo<XpWhitelist>> pageList(@RequestBody WhitelistPageListQuery query) { |
| | |
| | | xpWhitelistExportExcel.setNickName(xpWhitelist.getNickName()); |
| | | xpWhitelistExportExcel.setStatus(xpWhitelist.getStatus()); |
| | | xpWhitelistExportExcel.setIsPriorityDispatch(xpWhitelist.getIsPriorityDispatch()); |
| | | xpWhitelistExportExcel.setExpireTime(xpWhitelist.getExpireTime().toString()); |
| | | xpWhitelistExportExcel.setMaxOrderNum(xpWhitelist.getMaxOrderNum()); |
| | | |
| | | xpWhitelistExportExcel.setCreateBy(xpWhitelist.getCreateBy()); |
| | | LocalDateTime createTime = xpWhitelist.getCreateTime(); |
| | | // 将LocalDateTime转化为yyyy-MM-dd HH:mm:ss格式字符串 |
| | |
| | | |
| | | import javax.validation.constraints.NotBlank; |
| | | import javax.validation.constraints.NotNull; |
| | | import javax.validation.constraints.Size; |
| | | |
| | | /** |
| | | * <p> |
| | |
| | | |
| | | @ApiModelProperty(value = "名称", required = true) |
| | | @NotBlank(message = "名称不能为空") |
| | | @Size(max = 50, message = "名称长度不能超过50") |
| | | private String planName; |
| | | |
| | | @ApiModelProperty(value = "备注", required = true) |
| | | @Size(max = 200, message = "备注长度不能超过200") |
| | | private String remarks; |
| | | |
| | | @ApiModelProperty(value = "路线规划规则", required = true) |
| | |
| | | |
| | | import javax.validation.constraints.NotBlank; |
| | | import javax.validation.constraints.NotNull; |
| | | import javax.validation.constraints.Size; |
| | | |
| | | /** |
| | | * <p> |
| | |
| | | |
| | | @ApiModelProperty(value = "名称", required = true) |
| | | @NotBlank(message = "名称不能为空") |
| | | @Size(max = 50, message = "名称长度不能超过50") |
| | | private String planName; |
| | | |
| | | @ApiModelProperty(value = "备注", required = true) |
| | | @Size(max = 200, message = "备注长度不能超过200") |
| | | private String remarks; |
| | | |
| | | @ApiModelProperty(value = "路线规划规则", required = true) |
| | |
| | | package com.xiaopeng.operation.exportExcle; |
| | | |
| | | import com.xiaopeng.common.core.annotation.Excel; |
| | | import com.fasterxml.jackson.annotation.JsonFormat; |
| | | import io.swagger.annotations.ApiModel; |
| | | import io.swagger.annotations.ApiModelProperty; |
| | | import lombok.Data; |
| | |
| | | |
| | | @ApiModelProperty(value = "优先派单 1=是 0=否") |
| | | @cn.afterturn.easypoi.excel.annotation.Excel(name = "优先派单",width = 20,replace = {"是_true","否_false"}) |
| | | |
| | | private Boolean isPriorityDispatch; |
| | | |
| | | @ApiModelProperty(value = "有限期") |
| | | @JsonFormat(pattern = "yyyy-MM-dd") |
| | | @cn.afterturn.easypoi.excel.annotation.Excel(name = "有限期",width = 20) |
| | | private String expireTime; |
| | | |
| | | @ApiModelProperty(value = "最大下单数量") |
| | | @cn.afterturn.easypoi.excel.annotation.Excel(name = "最大下单数量",width = 20) |
| | | private Integer maxOrderNum; |
| | | |
| | | @ApiModelProperty(value = "记录创建人,前端忽略") |
| | | @cn.afterturn.easypoi.excel.annotation.Excel(name = "创建人",width = 20) |
| | |
| | | @ApiModelProperty(value = "选中的记录id集合") |
| | | private List<Long> ids; |
| | | |
| | | |
| | | |
| | | |
| | | } |
| | |
| | | import com.baomidou.mybatisplus.extension.service.IService; |
| | | import com.xiaopeng.api.model.operation.XpActicity; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * <p> |
| | | * 活动管理 服务类 |
| | |
| | | */ |
| | | public interface IXpActicityService extends IService<XpActicity> { |
| | | |
| | | |
| | | List<String> findListToIds(String acticityName); |
| | | } |
| | |
| | | package com.xiaopeng.operation.service.impl; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.xiaopeng.api.model.operation.XpActicity; |
| | | import com.xiaopeng.operation.mapper.XpActicityMapper; |
| | | import com.xiaopeng.operation.service.IXpActicityService; |
| | | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * <p> |
| | |
| | | @Service |
| | | public class XpActicityServiceImpl extends ServiceImpl<XpActicityMapper, XpActicity> implements IXpActicityService { |
| | | |
| | | |
| | | |
| | | @Override |
| | | public List<String> findListToIds(String acticityName){ |
| | | LambdaQueryWrapper<XpActicity > query = new LambdaQueryWrapper<>(); |
| | | query.select(XpActicity::getId); |
| | | query.eq(XpActicity::getDeleted, 0); |
| | | query.like(XpActicity::getActicityName, acticityName); |
| | | return listObjs(query, Object::toString); |
| | | } |
| | | |
| | | } |
| | |
| | | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import cn.hutool.core.util.PhoneUtil; |
| | | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
| | | import com.xiaopeng.api.dto.account.UcUserInfo; |
| | | import com.xiaopeng.api.feignClient.account.UserResourcesClient; |
| | | import com.xiaopeng.api.model.account.SysUser; |
| | | import com.xiaopeng.api.model.operation.XpWhitelist; |
| | | import com.xiaopeng.api.model.operation.XpWhitelistOperlog; |
| | |
| | | import com.xiaopeng.operation.mapper.XpWhitelistOperlogMapper; |
| | | import com.xiaopeng.operation.query.WhitelistPageListQuery; |
| | | import com.xiaopeng.operation.service.IXpWhitelistService; |
| | | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
| | | import org.apache.shardingsphere.transaction.annotation.ShardingSphereTransactionType; |
| | | import org.apache.shardingsphere.transaction.core.TransactionType; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | |
| | | import org.springframework.util.StringUtils; |
| | | |
| | | import java.time.LocalDate; |
| | | import java.util.ArrayList; |
| | | import java.util.Arrays; |
| | | import java.util.Comparator; |
| | | import java.util.HashMap; |
| | | import java.util.HashSet; |
| | | import java.util.LinkedHashSet; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Set; |
| | | import java.util.*; |
| | | import java.util.stream.Collectors; |
| | | |
| | | /** |
| | |
| | | |
| | | @Autowired |
| | | private XpWhitelistOperlogMapper xpWhitelistOperlogMapper; |
| | | @Autowired |
| | | private UserResourcesClient userResourcesClient; |
| | | |
| | | |
| | | @Override |
| | | public PageInfo<XpWhitelist> pageList(WhitelistPageListQuery query) { |
| | | PageInfo<XpWhitelist> pageInfo = new PageInfo<>(query.getPageNum(),query.getPageSize()); |
| | |
| | | public List<XpWhitelist> exportList(WhitelistPageListQuery query) { |
| | | List<XpWhitelist> list = this.baseMapper.exportList(query); |
| | | return list; |
| | | } |
| | | |
| | | //根据用户账号 通过接口获取用户id |
| | | public UcUserInfo getUserByAccount (String account){ |
| | | UcUserInfo userByMobile = userResourcesClient.getUserByMobile(account); |
| | | if(userByMobile == null){ |
| | | throw new ServiceException(account+"未获取到用户信息"); |
| | | } |
| | | return userByMobile; |
| | | } |
| | | |
| | | @Override |
| | |
| | | Map<String, Long> reasonCountMap = new HashMap<>(); |
| | | Map<String, LinkedHashSet<String>> reasonSampleMap = new HashMap<>(); |
| | | |
| | | Map<String, UcUserInfo> userMap = new HashMap<>(); |
| | | Set<String> seenAccounts = new HashSet<>(); |
| | | List<String> candidates = new ArrayList<>(); |
| | | for (String account : accounts) { |
| | |
| | | recordFail(reasonCountMap, reasonSampleMap, "文件内重复", account, sampleLimitPerReason); |
| | | continue; |
| | | } |
| | | candidates.add(account); |
| | | //根据用户账号 通过接口获取用户id |
| | | UcUserInfo userByAccount = this.getUserByAccount(account); |
| | | if(userByAccount != null){ |
| | | candidates.add(account); |
| | | userMap.put(account, userByAccount); |
| | | } |
| | | } |
| | | if (CollUtil.isEmpty(candidates)) { |
| | | return R.fail("所有帐号添加失败,请检查帐号格式或唯一性"); |
| | |
| | | } |
| | | XpWhitelist xpWhitelist = new XpWhitelist(); |
| | | xpWhitelist.setAccount(account); |
| | | xpWhitelist.setNickName(userMap.get(account).getNickName()); |
| | | xpWhitelist.setAppUserId(userMap.get(account).getUid()); |
| | | xpWhitelist.setStatus(true); |
| | | xpWhitelist.setIsPriorityDispatch(dto.getIsPriorityDispatch()); |
| | | xpWhitelist.setWhitelistSource(1); |
| | |
| | | if (!PhoneUtil.isMobile(dto.getAccount())) { |
| | | throw new ServiceException("以下帐号格式不正确:", I18nKeyConstant.ACCOUNT_FORMAT_ERROR, new Object[]{dto.getAccount()}); |
| | | } |
| | | UcUserInfo userByAccount = this.getUserByAccount(dto.getAccount()); |
| | | XpWhitelist old = this.lambdaQuery() |
| | | .eq(XpWhitelist::getAccount, dto.getAccount()) |
| | | .ne(XpWhitelist::getId, dto.getId()) |
| | |
| | | } |
| | | XpWhitelist whitelist = this.getById(dto.getId()); |
| | | |
| | | whitelist.setAppUserId(userByAccount.getUid()); |
| | | |
| | | StringBuilder operationContent = new StringBuilder("编辑账号:"); |
| | | StringBuilder accountBuilder = new StringBuilder(); |
| | | if (!whitelist.getAccount().equals(dto.getAccount())) { |
| | | accountBuilder.append("白名单账号" + "\"" + whitelist.getAccount() + "\"" + "为" + "\"" + dto.getAccount() + "\""); |
| | | } |
| | | if (StringUtils.hasLength(whitelist.getNickName()) || StringUtils.hasLength(dto.getNickName())) { |
| | | if (!StringUtils.hasLength(whitelist.getNickName())) { |
| | | if (StringUtils.hasLength(whitelist.getNickName())) { |
| | | if (!whitelist.getNickName().equals(dto.getNickName())) { |
| | | accountBuilder.append("用户名称" + "\"" + whitelist.getNickName() + "\"" + "为" + "\"" + dto.getNickName() + "\""); |
| | | } |
| | |
| | | if (whitelist.getIsPriorityDispatch() != dto.getIsPriorityDispatch()) { |
| | | accountBuilder.append("优先派单" + "\"" + (whitelist.getIsPriorityDispatch() ? "是" : "否") + "\"" + "为" + "\"" + (dto.getIsPriorityDispatch() ? "是" : "否") + "\""); |
| | | } |
| | | if ( !dto.getMaxOrderNum().equals(whitelist.getMaxOrderNum()) ) { |
| | | accountBuilder.append("最大下单数量" + "\"" + whitelist.getMaxOrderNum() + "\"" + "为" + "\"" + dto.getMaxOrderNum() + "\""); |
| | | whitelist.setMaxOrderNum(dto.getMaxOrderNum()); |
| | | } |
| | | if (!whitelist.getExpireTime().isEqual(dto.getExpireTime())) { |
| | | accountBuilder.append("有限期" + "\"" + whitelist.getExpireTime() + "\"" + "为" + "\"" + dto.getExpireTime() + "\""); |
| | | whitelist.setExpireTime(dto.getExpireTime()); |
| | | } |
| | | |
| | | XpWhitelistOperlog xpWhitelistOperlog = new XpWhitelistOperlog(); |
| | | xpWhitelistOperlog.setOperationName(sysUser.getNickName()); |
| | | xpWhitelistOperlog.setOperationAccount(sysUser.getPhoneNumber()); |
| | |
| | | log.info("没有找到空闲车辆 {},尝试连环派单给即将服务结束的车辆", orderNo); |
| | | // 查询状态为1的车辆vin |
| | | R<List<String>> servingVehicles = xpVehicleClient.queryVehiclePositionByVin(new VehiclePositionByVinDto(xpElectronicFence.getId(), vinList, xpTradingStrategy.getSimultaneouslyOrderCount(),1)); |
| | | if (vehiclePositionVos.getCode() != 200) { |
| | | if (servingVehicles.getCode() != 200) { |
| | | continue; |
| | | } |
| | | |
| | | LonLatDto current = new LonLatDto(Double.parseDouble(split[0]), Double.parseDouble(split[1]));//当前点位 |
| | | LonLatDto userPostion = new LonLatDto(Double.parseDouble(split[0]), Double.parseDouble(split[1])); |
| | | |
| | | XpTradingStrategyDetailVO strategyDetailVO = tradingStrategyClient.queryById().getData(); |
| | | |
| | |
| | | .map(R::getData) |
| | | .filter(o -> { |
| | | XpVehicle xpVehicle = xpVehicleClient.queryById(o.getVehicleId()).getData(); |
| | | |
| | | Object cacheObject = redisService.getCacheObject(CacheConstants.navigationEnd(o.getAppUserId().toString())); |
| | | RouteVo routeRemain = JSON.parseObject(cacheObject.toString(), RouteVo.class); |
| | | if (routeRemain.getDuration() / 60 > timeRegardAsEnding) { |
| | | return false; |
| | | } |
| | | RouteAndFareDto remain = new RouteAndFareDto(); |
| | | remain.setOrigin(current); |
| | | LonLatDto ll = new LonLatDto(o.getStartLon(), o.getStartLat()); |
| | | remain.setDestination(ll); |
| | | RouteVo r = xpMapClient.getDistanceAndDuration(remain).getData(); |
| | | if (r.getDistance() > distanceRegardAsEnding) { |
| | | if (routeRemain.getDistance() > distanceRegardAsEnding) { |
| | | return false; |
| | | } |
| | | if (r.getDuration() / 60 > timeRegardAsEnding) { |
| | | if (routeRemain.getDuration() / 60 > timeRegardAsEnding) { |
| | | return false; |
| | | } |
| | | RouteAndFareDto next = new RouteAndFareDto(); |
| | |
| | | RouteVo route = xpMapClient.getDistanceAndDuration(next).getData(); |
| | | if (route.getDistance() > backupPower + xpVehicle.getMileageRange()) |
| | | return false; |
| | | return r.getDistance() < destToPassenger |
| | | return routeRemain.getDistance() < destToPassenger |
| | | || now.plusSeconds(routeRemain.getDuration()) |
| | | .plusMinutes(endingTimeToUseTime) |
| | | .isBefore(rideTime); |
| | |
| | | XpVehicle xpVehicle = xpVehicleClient.queryById(luckyOder.getVehicleId()).getData(); |
| | | // 设置路线信息 |
| | | RouteAndFareDto routeAndFareDto = new RouteAndFareDto(); |
| | | routeAndFareDto.setDestination(current); |
| | | routeAndFareDto.setDestination(userPostion); |
| | | routeAndFareDto.setOrigin(new LonLatDto(luckyOder.getEndLon(),luckyOder.getEndLat())); |
| | | RouteVo routeToOrigin = xpMapClient.getDistanceAndDuration(routeAndFareDto).getData(); |
| | | Object cacheObject = redisService.getCacheObject(CacheConstants.navigationEnd(luckyOder.getAppUserId().toString())); |
| | |
| | | redisService.setCacheObject(CacheConstants.navigationStart(xpOrder.getAppUserId().toString()), routeToOrigin); |
| | | redisService.removeByPattern(quoteName,xpOrder.getOrderNumber()); |
| | | log.info("将订单 {} 从队列 {} 弹出去", orderNo, quoteName); |
| | | break; |
| | | } |
| | | } |
| | | return false; |
| | |
| | | |
| | | // 获取车辆的区域信息 |
| | | if (positionList.isEmpty()) { |
| | | continue; |
| | | log.info("没有找到空闲车辆 {},尝试连环派单给即将服务结束的车辆", orderNo); |
| | | // 查询状态为1的车辆vin |
| | | Set<String> vinList = positionList.stream().map(VehiclePosition::getVin).collect(Collectors.toSet()); |
| | | R<List<String>> servingVehicles = xpVehicleClient.queryVehiclePositionByVin(new VehiclePositionByVinDto(data.getFleetId(), vinList, xpTradingStrategy.getSimultaneouslyOrderCount(),1)); |
| | | if (servingVehicles.getCode() != 200) { |
| | | continue; |
| | | } |
| | | |
| | | LonLatDto userPosition = new LonLatDto(data.getStartLon(), data.getStartLat());//人的位置 |
| | | |
| | | XpTradingStrategyDetailVO strategyDetailVO = tradingStrategyClient.queryById().getData(); |
| | | |
| | | Integer distanceRegardAsEnding = strategyDetailVO.getDistanceRegardAsEnding(); |
| | | Integer timeRegardAsEnding = strategyDetailVO.getTimeRegardAsEnding(); |
| | | |
| | | Integer backupPower = strategyDetailVO.getBackupPower(); |
| | | Integer destToPassenger = strategyDetailVO.getDestToPassenger(); |
| | | Integer endingTimeToUseTime = strategyDetailVO.getEndingTimeToUseTime(); |
| | | LocalDateTime rideTime = data.getRideTime(); |
| | | LocalDateTime now = LocalDateTime.now(); |
| | | Optional<XpOrder> luckyOrderOpt = servingVehicles.getData().stream() |
| | | //获取位置 |
| | | .map(vin -> mongoTemplate.findOne(new Query(Criteria.where("vin").is(vin)), VehiclePosition.class)) |
| | | .map(VehiclePosition::getOrderId) |
| | | .map(xpOrderClient::queryOrderById) |
| | | .filter(R::isSuccess) |
| | | .map(R::getData) |
| | | .filter(o -> { |
| | | XpVehicle xpVehicle = xpVehicleClient.queryById(o.getVehicleId()).getData(); |
| | | Object cacheObject = redisService.getCacheObject(CacheConstants.navigationEnd(o.getAppUserId().toString())); |
| | | RouteVo routeRemain = JSON.parseObject(cacheObject.toString(), RouteVo.class); |
| | | if (routeRemain.getDuration() / 60 > timeRegardAsEnding) { |
| | | return false; |
| | | } |
| | | if (routeRemain.getDistance() > distanceRegardAsEnding) { |
| | | return false; |
| | | } |
| | | if (routeRemain.getDuration() / 60 > timeRegardAsEnding) { |
| | | return false; |
| | | } |
| | | RouteAndFareDto next = new RouteAndFareDto(); |
| | | next.setOrigin(new LonLatDto(o.getStartLon(),o.getStartLat())); |
| | | next.setDestination(new LonLatDto(o.getEndLon(),o.getEndLat())); |
| | | RouteVo route = xpMapClient.getDistanceAndDuration(next).getData(); |
| | | if (route.getDistance() > backupPower + xpVehicle.getMileageRange()) |
| | | return false; |
| | | return routeRemain.getDistance() < destToPassenger |
| | | || now.plusSeconds(routeRemain.getDuration()) |
| | | .plusMinutes(endingTimeToUseTime) |
| | | .isBefore(rideTime); |
| | | }) |
| | | .findAny(); |
| | | |
| | | if (luckyOrderOpt.isPresent()) { |
| | | XpOrder luckyOder = luckyOrderOpt.get(); |
| | | log.info("有空闲车辆 {} 订单 {}", luckyOder.getOrderNumber(), orderNo); |
| | | XpVehicle xpVehicle = xpVehicleClient.queryById(luckyOder.getVehicleId()).getData(); |
| | | // 设置路线信息 |
| | | RouteAndFareDto pickUpRoute = new RouteAndFareDto(); |
| | | pickUpRoute.setDestination(userPosition); |
| | | pickUpRoute.setOrigin(new LonLatDto(luckyOder.getEndLon(),luckyOder.getEndLat())); |
| | | RouteVo routeToOrigin = xpMapClient.getDistanceAndDuration(pickUpRoute).getData(); |
| | | Object cacheObject = redisService.getCacheObject(CacheConstants.navigationEnd(luckyOder.getAppUserId().toString())); |
| | | RouteVo routeRemain = JSON.parseObject(cacheObject.toString(), RouteVo.class); |
| | | routeToOrigin.getPolyline().addAll(0, routeRemain.getPolyline()); |
| | | routeToOrigin.setDuration(routeToOrigin.getDuration() + routeRemain.getDuration()); |
| | | routeToOrigin.setDistance(routeToOrigin.getDistance() + routeRemain.getDistance()); |
| | | PushOrderDto pushOrderDto = buildPushOrderDto(data); |
| | | pushOrderDto.setVin(xpVehicle.getVehicleVin()); |
| | | // 订单流转 |
| | | orderMissionClient.pushOrder(pushOrderDto); |
| | | redisService.setCacheObject(CacheConstants.nextOrder(luckyOder.getId().toString()), data.getId()); |
| | | redisService.setCacheObject(CacheConstants.navigationStart(data.getAppUserId().toString()), routeToOrigin); |
| | | break; |
| | | } |
| | | } |
| | | |
| | | // 直接派单 定义一个锁来锁定车辆 |
| | |
| | | |
| | | <!-- 通用查询结果列 --> |
| | | <sql id="Base_Column_List"> |
| | | id, nick_name, account, status, is_priority_dispatch, whitelist_source,expire_time, is_deleted, create_time, update_time, create_by, update_by |
| | | id, nick_name, account, status, is_priority_dispatch, whitelist_source,expire_time,max_order_num, is_deleted, create_time, update_time, create_by, update_by |
| | | </sql> |
| | | <select id="pageList" resultType="com.xiaopeng.api.model.operation.XpWhitelist"> |
| | | select |
| | | id,nick_name as nickName,account,status,is_priority_dispatch as isPriorityDispatch,whitelist_source as whitelistSource,expire_time as expireTime |
| | | id,nick_name as nickName,account,status,is_priority_dispatch as isPriorityDispatch,whitelist_source as whitelistSource, |
| | | expire_time as expireTime,max_order_num as maxOrderNum |
| | | ,create_time as createTime,update_time as updateTime,create_by as createBy,update_by as updateBy |
| | | from xp_whitelist |
| | | where is_deleted = 0 |
| | |
| | | order by create_time desc |
| | | </select> |
| | | <select id="exportList" resultType="com.xiaopeng.api.model.operation.XpWhitelist"> |
| | | select id,nick_name as nickName,account,status,is_priority_dispatch as isPriorityDispatch,whitelist_source as whitelistSource,expire_time as expireTime |
| | | select id,nick_name as nickName,account,status,is_priority_dispatch as isPriorityDispatch,whitelist_source as whitelistSource, |
| | | expire_time as expireTime,max_order_num as maxOrderNum |
| | | ,create_time as createTime,update_time as updateTime,create_by as createBy,update_by as updateBy |
| | | from xp_whitelist |
| | | where is_deleted = 0 |
| | |
| | | import com.xiaopeng.api.vo.operation.RouteVo; |
| | | import com.xiaopeng.api.vo.order.*; |
| | | import com.xiaopeng.common.core.domain.R; |
| | | import com.xiaopeng.common.core.exception.ServiceException; |
| | | import com.xiaopeng.common.core.utils.StringUtils; |
| | | import com.xiaopeng.common.core.utils.WebUtils; |
| | | import com.xiaopeng.common.core.web.page.PageInfo; |
| | | import com.xiaopeng.common.redis.service.RedisService; |
| | | import com.xiaopeng.order.mangodb.model.OrderTrajectory; |
| | | import com.xiaopeng.order.mangodb.service.OrderTrajectoryService; |
| | | import com.xiaopeng.order.model.dto.XpOrderCommentDto; |
| | | import com.xiaopeng.order.model.dto.XpOrderOperateDTO; |
| | | import com.xiaopeng.order.model.dto.XpOrderPayDto; |
| | |
| | | import java.util.ArrayList; |
| | | import java.util.Arrays; |
| | | import java.util.List; |
| | | import java.util.Objects; |
| | | |
| | | /** |
| | | * <p> |
| | |
| | | private MongoTemplate mongoTemplate; |
| | | @Resource |
| | | private XpVehicleClient xpVehicleClient; |
| | | private OrderTrajectoryService orderTrajectoryService; |
| | | |
| | | /** |
| | | * 获取订单统计数据(内部接口) |
| | |
| | | */ |
| | | @ApiOperation(value = "订单分页列表", tags = {"管理后台-交易管理-订单管理"}) |
| | | @PostMapping("/api/v1/order/pageList") |
| | | public R<PageInfo<XpOrderPageVO>> pageList(@Validated @RequestBody XpOrderPageQuery query) { |
| | | public R<PageInfo<XpOrderPageVO>> pageList( @RequestBody XpOrderPageQuery query) { |
| | | PageInfo<XpOrderPageVO> pageInfo = xpOrderService.pageList(query); |
| | | return R.ok(pageInfo); |
| | | } |
| | |
| | | String uid = request.getHeader("XP-Uid"); |
| | | OrderDetailVo orderDetailVo = xpOrderService.orderDetail(orderId, uid); |
| | | BigDecimal orderMoney = orderDetailVo.getOrderMoney(); |
| | | if(orderMoney==null){ |
| | | if (orderMoney == null) { |
| | | orderDetailVo.setOrderMoneyStr("0.00"); |
| | | }else { |
| | | } else { |
| | | orderDetailVo.setOrderMoneyStr(orderMoney.setScale(2, RoundingMode.HALF_UP).toString()); |
| | | } |
| | | return R.ok(orderDetailVo); |
| | |
| | | @ApiOperation(value = "获取历史下单成功起点10条数据列表", tags = {"小程序-获取历史下单成功起点10条数据列表"}) |
| | | @GetMapping("/base/v1/mini-program/history/startPoints") |
| | | @XpClientCheck(checkToken = false, checkOtpToken = true) |
| | | public R<List<XpHistoryStartPointsVo>> getHistoryStartPoints(double lon, double lat,Long electronicAreaId, HttpServletRequest request) { |
| | | public R<List<XpHistoryStartPointsVo>> getHistoryStartPoints(double lon, double lat, Long electronicAreaId, HttpServletRequest request) { |
| | | // uid已通过拦截器otp校验,可以直接使用 |
| | | String uid = request.getHeader("XP-Uid"); |
| | | return R.ok(xpOrderService.getHistoryStartPoints(uid, lon, lat,electronicAreaId)); |
| | | return R.ok(xpOrderService.getHistoryStartPoints(uid, lon, lat, electronicAreaId)); |
| | | } |
| | | |
| | | /** |
| | |
| | | @ApiOperation(value = "获取历史下单成功终点10条数据列表", tags = {"小程序-获取历史下单成功终点10条数据列表"}) |
| | | @GetMapping("/base/v1/mini-program/history/endPoints") |
| | | @XpClientCheck(checkToken = false, checkOtpToken = true) |
| | | public R<List<XpHistoryEndPointsVo>> getHistoryEndPoints(double lon, double lat,Long electronicAreaId, HttpServletRequest request) { |
| | | public R<List<XpHistoryEndPointsVo>> getHistoryEndPoints(double lon, double lat, Long electronicAreaId, HttpServletRequest request) { |
| | | // uid已通过拦截器otp校验,可以直接使用 |
| | | String uid = request.getHeader("XP-Uid"); |
| | | return R.ok(xpOrderService.getHistoryEndPoints(uid, lon, lat,electronicAreaId)); |
| | | return R.ok(xpOrderService.getHistoryEndPoints(uid, lon, lat, electronicAreaId)); |
| | | } |
| | | |
| | | |
| | |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * + |
| | | * 获取行程费用明细 |
| | |
| | | */ |
| | | @ApiOperation(value = "行驶中车辆异常,订单状态修改- 修改状态=7 再修改 金额-再将状态修改成8") |
| | | @PostMapping("/logan/inner/v1/order/carAbnormalUpdateOrderStatus") |
| | | public R<String> carAbnormalUpdateOrderStatus(@RequestParam Long orderId ) { |
| | | public R<String> carAbnormalUpdateOrderStatus(@RequestParam Long orderId) { |
| | | // Integer status = OrderStatusEnum.SEE_OFF_GUESTS.getCode(); |
| | | return xpOrderService.carAbnormalUpdateOrderStatus(orderId); |
| | | } |
| | |
| | | @PostMapping("/api/v1/mini-program/order/queryOrderRoute") |
| | | @XpClientCheck(checkToken = false, checkOtpToken = true) |
| | | public R<RouteVo> queryOrderRoute(@Validated @RequestBody QueryOrderRouteDTO dto, HttpServletRequest request) { |
| | | if(dto.getOrderId() == null){ |
| | | if (dto.getOrderId() == null) { |
| | | return R.fail("参数不能为空"); |
| | | } |
| | | String uid = request.getHeader("XP-Uid"); |
| | |
| | | routeVo.setDirection(null != vehiclePosition ? vehiclePosition.getCourse() : 90); |
| | | return R.ok(routeVo); |
| | | } |
| | | |
| | | /** |
| | | * 订单查询轨迹路线 |
| | | */ |
| | |
| | | @GetMapping("/api/v1/mini-program/order/queryOrderTrajectory") |
| | | @XpClientCheck(checkToken = false, checkOtpToken = true) |
| | | public R<RouteVo> queryOrderTrajectory(@RequestParam String orderId, HttpServletRequest request) { |
| | | if(orderId == null){ |
| | | if (orderId == null) { |
| | | return R.fail("参数不能为空"); |
| | | } |
| | | List<OrderTrajectory> trajectories = mongoTemplate.find( |
| | |
| | | // 封装信息 |
| | | RouteVo routeVo = new RouteVo(); |
| | | List<LonLatDto> polyline = new ArrayList<>(); |
| | | if(!CollectionUtils.isEmpty(trajectories)){ |
| | | if (!CollectionUtils.isEmpty(trajectories)) { |
| | | for (OrderTrajectory trajectory : trajectories) { |
| | | LonLatDto lonLatDto = new LonLatDto(); |
| | | String[] split = trajectory.getLocation().split(","); |
| | |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * 当日取消订单次数 |
| | | * |
| | |
| | | * @return 订单数量 |
| | | */ |
| | | @PostMapping("/inner/order/todayCancellationCount") |
| | | public R<Integer> todayCancellationCount(@RequestParam Long userId){ |
| | | public R<Integer> todayCancellationCount(@RequestParam Long userId) { |
| | | long count = xpOrderService.count(new QueryWrapper<XpOrder>().eq("app_user_id", userId).eq("status", 0) |
| | | .eq("is_deleted", 0).last(" and DATE_FORMAT(create_time, '%Y-%m-%d') = DATE_FORMAT(NOW(), '%Y-%m-%d')")); |
| | | return R.ok((int)count); |
| | | return R.ok((int) count); |
| | | } |
| | | |
| | | |
| | | |
| | | |
| | | } |
| | | @ApiOperation(value = "通过订单id获取蓝牙开锁秘钥", tags = {"小程序-业务流程"}) |
| | | @GetMapping("/api/v1/mini-program/bluetooth/getUnlocktToken/{orderId}") |
| | | @XpClientCheck(checkToken = false, checkOtpToken = true) |
| | | public R<String> getBluetoothToken(@PathVariable("orderId") Long orderId, HttpServletRequest request) { |
| | | String uid = request.getHeader("XP-Uid"); |
| | | XpOrder xpOrder = xpOrderService.getById(orderId); |
| | | if (!Objects.equals(xpOrder.getAppUserId(), Long.valueOf(uid))) { |
| | | return R.ok(); |
| | | } |
| | | if(!Arrays.asList(4, 5, 6, 7, 8).contains(xpOrder.getStatus())){ |
| | | return R.ok(); |
| | | } |
| | | String token = redisService.getCacheObject("rt:bluetooth:unlock:order:" + orderId); |
| | | if (StringUtils.isBlank(token)) { |
| | | return R.ok(); |
| | | } |
| | | return R.ok(token); |
| | | } |
| | | } |
| | |
| | | import com.xiaopeng.api.dto.statistics.OrderStatisticsDTO; |
| | | import com.xiaopeng.api.dto.statistics.StatisticsQueryDTO; |
| | | import com.xiaopeng.api.dto.vehicleConvoy.BaseVehicleUserDto; |
| | | import com.xiaopeng.api.feignClient.account.SysGroupMenuClient; |
| | | import com.xiaopeng.api.feignClient.account.UserResourcesClient; |
| | | import com.xiaopeng.api.feignClient.account.XpAppUserCouponClient; |
| | | import com.xiaopeng.api.feignClient.automaticPilot.OrderMissionClient; |
| | |
| | | import com.xiaopeng.api.feignClient.map.XpMapClient; |
| | | import com.xiaopeng.api.feignClient.operation.*; |
| | | import com.xiaopeng.api.feignClient.pushMessage.XpPushMessageClient; |
| | | import com.xiaopeng.api.feignClient.vehicleConvoy.UserVehicleClient; |
| | | import com.xiaopeng.api.feignClient.vehicleConvoy.XpFleetClient; |
| | | import com.xiaopeng.api.feignClient.vehicleConvoy.XpFleetVehicleClient; |
| | | import com.xiaopeng.api.feignClient.vehicleConvoy.XpVehicleClient; |
| | | import com.xiaopeng.api.feignClient.vehicleConvoy.*; |
| | | import com.xiaopeng.api.model.maintenance.XpTaskRule; |
| | | import com.xiaopeng.api.model.operation.*; |
| | | import com.xiaopeng.api.model.order.*; |
| | |
| | | import com.xiaopeng.api.vo.operation.RouteVo; |
| | | import com.xiaopeng.api.vo.order.*; |
| | | import com.xiaopeng.api.vo.vehicleConvoy.AddTaxiPassengerVo; |
| | | import com.xiaopeng.api.vo.vehicleConvoy.BluetoothDeliverVo; |
| | | import com.xiaopeng.api.vo.vehicleConvoy.CancelTaxiPassengerVo; |
| | | import com.xiaopeng.common.core.constant.CacheConstants; |
| | | import com.xiaopeng.common.core.constant.Constants; |
| | |
| | | import com.xiaopeng.order.model.query.XpOrderPageQuery; |
| | | import com.xiaopeng.order.model.vo.*; |
| | | import com.xiaopeng.order.service.*; |
| | | import com.xiaopeng.order.utils.GDMapGeocodingUtil; |
| | | import com.xiaopeng.order.utils.RegionCodeFilterUtil; |
| | | import com.xiaopeng.order.utils.TaxiPricingDemoService; |
| | | import com.xiaopeng.order.utils.XpPricingUtil; |
| | | import com.xiaopeng.order.utils.*; |
| | | import com.xiaopeng.order.utils.holiday.HolidayUtil; |
| | | import com.xiaopeng.order.vo.OrderDetailVo; |
| | | import lombok.RequiredArgsConstructor; |
| | |
| | | @Resource |
| | | @Lazy |
| | | private BootCommonService bootCommonService; |
| | | @Autowired |
| | | private SysGroupMenuClient sysGroupMenuClient; |
| | | |
| | | |
| | | @Autowired |
| | | private MongoTemplate mongoTemplate; |
| | | @Autowired |
| | | private HolidayUtil holidayUtil; |
| | | |
| | | @Autowired |
| | | private BluetoothUnlockTokenUtil bluetoothUnlockTokenUtil; |
| | | |
| | | @Resource |
| | | private BluetoothUnlockClient bluetoothUnlockClient; |
| | | |
| | | @Value("${robotaxi.appId:xp_robotaxi_api}") |
| | | private String appid; |
| | |
| | | if (Objects.nonNull(order.getAppUserId()) && userInfoMap != null && userInfoMap.containsKey(order.getAppUserId().toString())) { |
| | | UcUserInfo userInfo = userInfoMap.get(order.getAppUserId().toString()); |
| | | vo.setNickName(userInfo.getNickName()); |
| | | vo.setPhoneNumber(userInfo.getMobile()); |
| | | vo.setPhoneNumber(sysGroupMenuClient.getPhoneDesensitization(userInfo.getMobile()).getData()); |
| | | } |
| | | |
| | | // 车辆信息 |
| | |
| | | Long userId = order.getAppUserId(); |
| | | if (order.getStatus() == 1) { |
| | | order.setStatus(0); |
| | | Long appUserId = order.getAppUserId(); |
| | | order.setAppUserId(null); |
| | | baseMapper.updateById(order); |
| | | order.setAppUserId(appUserId); |
| | | redisService.setCacheObject("ORDER_STATUS:" + order.getId(), 0, 24L, TimeUnit.HOURS); |
| | | //删除蓝牙秘钥 |
| | | String token = redisService.getCacheObject("rt:bluetooth:unlock:order:" + order.getId()); |
| | | if(StringUtils.isNotBlank(token)){ |
| | | redisService.deleteObject("rt:bluetooth:unlock:" + token); |
| | | redisService.deleteObject("rt:bluetooth:unlock:order:" + order.getId()); |
| | | XpVehicle xpVehicle = xpVehicleClient.queryById(order.getVehicleId()).getData(); |
| | | BluetoothDeliverVo vo = new BluetoothDeliverVo(); |
| | | vo.setVim(xpVehicle.getVehicleVin()); |
| | | vo.setPassengerUid(order.getAppUserId()); |
| | | BaseVehicleUserDto delete = bluetoothUnlockClient.delete(vo); |
| | | if(null == delete || delete.getCode() != 0){ |
| | | log.error("删除蓝牙解锁失败:{} {}", delete.getMsg(), order.getOrderNumber()); |
| | | } |
| | | } |
| | | // 消息推送 |
| | | Map<String, Object> params = new HashMap<>(); |
| | | Map<String, Object> content = new HashMap<>(); |
| | |
| | | addCruiseMission(xpVehicle.getId()); |
| | | } |
| | | order.setStatus(0); |
| | | Long appUserId = order.getAppUserId(); |
| | | order.setAppUserId(null); |
| | | baseMapper.updateById(order); |
| | | order.setAppUserId(appUserId); |
| | | redisService.setCacheObject("ORDER_STATUS:" + order.getId(), 0, 24L, TimeUnit.HOURS); |
| | | //删除蓝牙秘钥 |
| | | String token = redisService.getCacheObject("rt:bluetooth:unlock:order:" + order.getId()); |
| | | if(StringUtils.isNotBlank(token)){ |
| | | redisService.deleteObject("rt:bluetooth:unlock:" + token); |
| | | redisService.deleteObject("rt:bluetooth:unlock:order:" + order.getId()); |
| | | XpVehicle xpVehicle = xpVehicleClient.queryById(order.getVehicleId()).getData(); |
| | | BluetoothDeliverVo vo = new BluetoothDeliverVo(); |
| | | vo.setVim(xpVehicle.getVehicleVin()); |
| | | vo.setPassengerUid(order.getAppUserId()); |
| | | BaseVehicleUserDto delete = bluetoothUnlockClient.delete(vo); |
| | | if(null == delete || delete.getCode() != 0){ |
| | | log.error("删除蓝牙解锁失败:{} {}", delete.getMsg(), order.getOrderNumber()); |
| | | } |
| | | } |
| | | |
| | | XpOrderCancel xpOrderCancel = new XpOrderCancel(); |
| | | xpOrderCancel.setOrderId(orderId); |
| | |
| | | order.setStatus(0); |
| | | order.setAppUserId(null); |
| | | baseMapper.updateById(order); |
| | | order.setAppUserId(appUserId); |
| | | redisService.setCacheObject("ORDER_STATUS:" + order.getId(), 0, 24L, TimeUnit.HOURS); |
| | | //删除蓝牙秘钥 |
| | | String token = redisService.getCacheObject("rt:bluetooth:unlock:order:" + order.getId()); |
| | | if(StringUtils.isNotBlank(token)){ |
| | | redisService.deleteObject("rt:bluetooth:unlock:" + token); |
| | | redisService.deleteObject("rt:bluetooth:unlock:order:" + order.getId()); |
| | | XpVehicle xpVehicle = xpVehicleClient.queryById(order.getVehicleId()).getData(); |
| | | BluetoothDeliverVo vo = new BluetoothDeliverVo(); |
| | | vo.setVim(xpVehicle.getVehicleVin()); |
| | | vo.setPassengerUid(order.getAppUserId()); |
| | | BaseVehicleUserDto delete = bluetoothUnlockClient.delete(vo); |
| | | if(null == delete || delete.getCode() != 0){ |
| | | log.error("删除蓝牙解锁失败:{} {}", delete.getMsg(), order.getOrderNumber()); |
| | | } |
| | | } |
| | | |
| | | XpOrderCancel xpOrderCancel = new XpOrderCancel(); |
| | | xpOrderCancel.setOrderId(Long.valueOf(orderId)); |
| | | xpOrderCancel.setUserId(1l); |
| | |
| | | throw new RuntimeException("订单状态错误"); |
| | | } |
| | | order.setStatus(8); |
| | | Long appUserId = order.getAppUserId(); |
| | | order.setAppUserId(null); |
| | | order.setTripEnd(LocalDateTime.now()); |
| | | baseMapper.updateById(order); |
| | | order.setAppUserId(appUserId); |
| | | redisService.setCacheObject("ORDER_STATUS:" + order.getId(), 8, 24L, TimeUnit.HOURS); |
| | | //删除蓝牙秘钥 |
| | | String token = redisService.getCacheObject("rt:bluetooth:unlock:order:" + orderId); |
| | | if(StringUtils.isNotBlank(token)){ |
| | | redisService.deleteObject("rt:bluetooth:unlock:" + token); |
| | | redisService.deleteObject("rt:bluetooth:unlock:order:" + orderId); |
| | | XpVehicle xpVehicle = xpVehicleClient.queryById(order.getVehicleId()).getData(); |
| | | BluetoothDeliverVo vo = new BluetoothDeliverVo(); |
| | | vo.setVim(xpVehicle.getVehicleVin()); |
| | | vo.setPassengerUid(order.getAppUserId()); |
| | | BaseVehicleUserDto delete = bluetoothUnlockClient.delete(vo); |
| | | if(null == delete || delete.getCode() != 0){ |
| | | log.error("删除蓝牙解锁失败:{} {}", delete.getMsg(), order.getOrderNumber()); |
| | | } |
| | | } |
| | | |
| | | // 根据订单所属开通城市id 查询定价 |
| | | XpPricing pricing = bootCommonService.getXpPricing(order.getCityId(),order.getVehicleId()); |
| | |
| | | |
| | | //推送乘客车辆到达预约点的导航数据 |
| | | sendRoute(addTaxiPassengerVo.getUid(), orderId, "ORDER_NAVIGATION_START_DATA", dto.getXpVehicleVin()); |
| | | |
| | | //生成蓝牙token |
| | | //生成过期时间 (接单时间+到达上车点预估时间+上车点等待时间+行程预估时间+下车点等待时间+30分钟) |
| | | XpPoint startPoint = xpPointClient.queryById(order.getStartPointId()).getData(); |
| | | Object cacheObject = redisService.getCacheObject("ORDER_NAVIGATION_END_DATA:" + appUserId); |
| | | RouteVo routeVo1 = JSON.parseObject(cacheObject.toString(), RouteVo.class); |
| | | XpPoint endPoint = xpPointClient.queryById(order.getEndPointId()).getData(); |
| | | LocalDateTime localDateTime = order.getAcceptanceTime().plusMinutes(routeVo.getDuration()).plusMinutes(startPoint.getParkingTimeLimit()) |
| | | .plusMinutes(routeVo1.getDuration()).plusMinutes(endPoint.getParkingTimeLimit()).plusMinutes(30); |
| | | long expirationTime = localDateTime.toEpochSecond(ZoneOffset.of("+8")); |
| | | //生成token |
| | | String token = bluetoothUnlockTokenUtil.generateBluetoothUnlockToken(order.getAppUserId(), |
| | | order.getId(), order.getVehicleId(), expirationTime); |
| | | log.info("生成蓝牙token:{} 有效期:{} 订单号: {}", token, expirationTime, order.getOrderNumber()); |
| | | //存储缓存 |
| | | JSONObject jsonObject = new JSONObject(); |
| | | jsonObject.put("userId", order.getAppUserId()); |
| | | jsonObject.put("orderId", order.getId()); |
| | | jsonObject.put("vehicleId", order.getVehicleId()); |
| | | jsonObject.put("expirationTime", expirationTime); |
| | | jsonObject.put("orderNumber", order.getOrderNumber()); |
| | | jsonObject.put("vehicleVin", vehicle.getVehicleVin()); |
| | | jsonObject.put("createTime", System.currentTimeMillis()); |
| | | redisService.setCacheObject("rt:bluetooth:unlock:" + token, jsonObject.toJSONString(), expirationTime, TimeUnit.SECONDS); |
| | | redisService.setCacheObject("rt:bluetooth:unlock:order:" + order.getId(), token, expirationTime, TimeUnit.SECONDS); |
| | | //开始下发蓝牙token |
| | | BluetoothDeliverVo vo = new BluetoothDeliverVo(); |
| | | vo.setCredential(token); |
| | | vo.setVim(vehicle.getVehicleVin()); |
| | | vo.setPassengerUid(order.getAppUserId()); |
| | | BaseVehicleUserDto deliver = bluetoothUnlockClient.deliver(vo); |
| | | if(null == deliver){ |
| | | throw new ServiceException("下发蓝牙token失败"); |
| | | } |
| | | if(deliver.getCode() != 0){ |
| | | log.error("下发蓝牙token失败:{} {}", deliver.getMsg(), order.getOrderNumber()); |
| | | throw new ServiceException(deliver.getMsg()); |
| | | } |
| | | log.info("下发蓝牙token成功:{} {}", deliver.getData(), order.getOrderNumber()); |
| | | } |
| | | |
| | | if(order.getStatus() == 2){ |
| New file |
| | |
| | | package com.xiaopeng.order.utils; |
| | | |
| | | import org.springframework.beans.factory.annotation.Value; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | import javax.crypto.Mac; |
| | | import javax.crypto.spec.SecretKeySpec; |
| | | import java.security.InvalidKeyException; |
| | | import java.security.NoSuchAlgorithmException; |
| | | import java.security.SecureRandom; |
| | | |
| | | /** |
| | | * 蓝牙解锁Token生成工具类 |
| | | * 根据设计方案实现Token的生成 |
| | | */ |
| | | @Component |
| | | public class BluetoothUnlockTokenUtil { |
| | | |
| | | private static final String HMAC_ALGORITHM = "HmacSHA256"; |
| | | private static final int RANDOM_BYTES_LENGTH = 16; // 16字节随机数 |
| | | private static final int HMAC_BYTES_LENGTH = 16; // 16字节HMAC (取前16字节) |
| | | private static final int LONG_BYTE_SIZE = 8; // Long类型占用8字节 |
| | | |
| | | @Value("${bluetooth.unlock.secret:123456}") |
| | | private String secret; |
| | | |
| | | /** |
| | | * 生成蓝牙解锁Token |
| | | * |
| | | * @param userId 用户ID |
| | | * @param orderId 订单ID |
| | | * @param vehicleId 车辆ID |
| | | * @param expirationTime 订单预估结束时间 |
| | | * @return 64字符的Token (32字符随机数 + 32字符HMAC) |
| | | */ |
| | | public String generateBluetoothUnlockToken(long userId, long orderId, |
| | | long vehicleId, long expirationTime) { |
| | | try { |
| | | // 1. 使用安全随机数生成器生成16字节随机数 |
| | | SecureRandom secureRandom = new SecureRandom(); |
| | | byte[] randomBytes = new byte[RANDOM_BYTES_LENGTH]; |
| | | secureRandom.nextBytes(randomBytes); |
| | | |
| | | // 2. 拼接二进制数据:随机数(16字节) + userId(8字节) + orderId(8字节) + vehicleId(8字节) + expirationTime(8字节) |
| | | byte[] dataToSign = new byte[RANDOM_BYTES_LENGTH + 4 * LONG_BYTE_SIZE]; |
| | | |
| | | // 复制随机数 |
| | | System.arraycopy(randomBytes, 0, dataToSign, 0, RANDOM_BYTES_LENGTH); |
| | | |
| | | // 复制userId (转换为字节数组) |
| | | longToBytes(userId, dataToSign, RANDOM_BYTES_LENGTH); |
| | | |
| | | // 复制orderId (转换为字节数组) |
| | | longToBytes(orderId, dataToSign, RANDOM_BYTES_LENGTH + LONG_BYTE_SIZE); |
| | | |
| | | // 复制vehicleId (转换为字节数组) |
| | | longToBytes(vehicleId, dataToSign, RANDOM_BYTES_LENGTH + 2 * LONG_BYTE_SIZE); |
| | | |
| | | // 复制expirationTime (转换为字节数组) |
| | | longToBytes(expirationTime, dataToSign, RANDOM_BYTES_LENGTH + 3 * LONG_BYTE_SIZE); |
| | | |
| | | // 3. 使用服务器端密钥对上述48字节计算HMAC-SHA256 |
| | | byte[] hmacFull = calculateHmac(secret, dataToSign); |
| | | |
| | | // 4. 取HMAC结果的前16字节 |
| | | byte[] hmacPrefix = new byte[HMAC_BYTES_LENGTH]; |
| | | System.arraycopy(hmacFull, 0, hmacPrefix, 0, HMAC_BYTES_LENGTH); |
| | | |
| | | // 5. 最终Token = 十六进制(随机数) + 十六进制(HMAC前16字节) |
| | | String randomHex = bytesToHex(randomBytes); |
| | | String hmacHex = bytesToHex(hmacPrefix); |
| | | |
| | | return randomHex + hmacHex; |
| | | |
| | | } catch (NoSuchAlgorithmException | InvalidKeyException e) { |
| | | throw new RuntimeException("生成Token失败", e); |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * 将long值转换为8字节,并存入指定位置 |
| | | * |
| | | * @param value 要转换的long值 |
| | | * @param dest 目标字节数组 |
| | | * @param offset 偏移量 |
| | | */ |
| | | private static void longToBytes(long value, byte[] dest, int offset) { |
| | | for (int i = 0; i < LONG_BYTE_SIZE; i++) { |
| | | dest[offset + i] = (byte) ((value >> (8 * (LONG_BYTE_SIZE - 1 - i))) & 0xFF); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 计算HMAC-SHA256 |
| | | * |
| | | * @param secret 密钥 |
| | | * @param data 待计算的数据 |
| | | * @return HMAC-SHA256结果 |
| | | * @throws NoSuchAlgorithmException |
| | | * @throws InvalidKeyException |
| | | */ |
| | | private static byte[] calculateHmac(String secret, byte[] data) throws NoSuchAlgorithmException, InvalidKeyException { |
| | | SecretKeySpec secretKeySpec = new SecretKeySpec(secret.getBytes(), HMAC_ALGORITHM); |
| | | Mac mac = Mac.getInstance(HMAC_ALGORITHM); |
| | | mac.init(secretKeySpec); |
| | | return mac.doFinal(data); |
| | | } |
| | | |
| | | /** |
| | | * 将字节数组转换为十六进制字符串 |
| | | * |
| | | * @param bytes 字节数组 |
| | | * @return 十六进制字符串 |
| | | */ |
| | | private static String bytesToHex(byte[] bytes) { |
| | | StringBuilder sb = new StringBuilder(); |
| | | for (byte b : bytes) { |
| | | String hex = Integer.toHexString(b & 0xFF); |
| | | if (hex.length() == 1) { |
| | | sb.append('0'); |
| | | } |
| | | sb.append(hex); |
| | | } |
| | | return sb.toString(); |
| | | } |
| | | |
| | | } |
| | |
| | | |
| | | import com.alibaba.nacos.common.utils.CollectionUtils; |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.xiaopeng.api.dto.vehicleConvoy.BaseVehicleUserDto; |
| | | import com.xiaopeng.api.feignClient.vehicleConvoy.BluetoothUnlockClient; |
| | | import com.xiaopeng.api.feignClient.vehicleConvoy.XpVehicleClient; |
| | | import com.xiaopeng.api.model.order.XpOrder; |
| | | import com.xiaopeng.api.model.vehicleConvoy.XpVehicle; |
| | | import com.xiaopeng.api.vo.vehicleConvoy.BluetoothDeliverVo; |
| | | import com.xiaopeng.common.core.utils.StringUtils; |
| | | import com.xiaopeng.common.redis.service.RedisService; |
| | | import com.xiaopeng.order.service.IXpOrderService; |
| | | import lombok.extern.slf4j.Slf4j; |
| | |
| | | import org.springframework.scheduling.annotation.Scheduled; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | import javax.annotation.Resource; |
| | | import java.time.LocalDateTime; |
| | | import java.util.List; |
| | | import java.util.concurrent.TimeUnit; |
| | |
| | | |
| | | @Autowired |
| | | private RedisService redisService; |
| | | |
| | | @Resource |
| | | private XpVehicleClient xpVehicleClient; |
| | | |
| | | @Resource |
| | | private BluetoothUnlockClient bluetoothUnlockClient; |
| | | |
| | | /** |
| | | * 30秒执行一次 |
| | |
| | | order.setAppUserId(null); |
| | | order.setStatus(0); |
| | | xpOrderService.updateById(order); |
| | | order.setAppUserId(appUserId); |
| | | redisService.setCacheObject("ORDER_STATUS:" + order.getId(), 0, 24L, TimeUnit.HOURS); |
| | | //删除蓝牙秘钥 |
| | | String token = redisService.getCacheObject("rt:bluetooth:unlock:order:" + order.getId()); |
| | | if(StringUtils.isNotBlank(token)){ |
| | | redisService.deleteObject("rt:bluetooth:unlock:" + token); |
| | | redisService.deleteObject("rt:bluetooth:unlock:order:" + order.getId()); |
| | | XpVehicle xpVehicle = xpVehicleClient.queryById(order.getVehicleId()).getData(); |
| | | BluetoothDeliverVo vo = new BluetoothDeliverVo(); |
| | | vo.setVim(xpVehicle.getVehicleVin()); |
| | | vo.setPassengerUid(order.getAppUserId()); |
| | | BaseVehicleUserDto delete = bluetoothUnlockClient.delete(vo); |
| | | if(null == delete || delete.getCode() != 0){ |
| | | log.error("删除蓝牙解锁失败:{} {}", delete.getMsg(), order.getOrderNumber()); |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | |
| | | package com.xiaopeng.pushMessage.webSocket.config; |
| | | |
| | | import com.xiaopeng.pushMessage.webSocket.handler.PushMessageWebSocketHandler; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.context.annotation.Configuration; |
| | | import org.springframework.web.socket.config.annotation.EnableWebSocket; |
| | | import org.springframework.web.socket.config.annotation.WebSocketConfigurer; |
| | | import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; |
| | | |
| | | /** |
| | | * Spring WebSocket 配置 |
| | | * 注册 /websocket 端点,共用 Spring Boot HTTP 端口 |
| | | */ |
| | | @Configuration |
| | | @EnableWebSocket |
| | | public class WebSocketConfig implements WebSocketConfigurer { |
| | | |
| | | @Autowired |
| | | private PushMessageWebSocketHandler pushMessageWebSocketHandler; |
| | | |
| | | @Override |
| | | public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { |
| | | registry.addHandler(pushMessageWebSocketHandler, "/websocket") |
| | | // 允许所有跨域来源,生产环境可按需收紧 |
| | | .setAllowedOrigins("*"); |
| | | } |
| | | } |
| | | //package com.xiaopeng.pushMessage.webSocket.config; |
| | | // |
| | | //import com.xiaopeng.pushMessage.webSocket.handler.PushMessageWebSocketHandler; |
| | | //import org.springframework.beans.factory.annotation.Autowired; |
| | | //import org.springframework.context.annotation.Configuration; |
| | | //import org.springframework.web.socket.config.annotation.EnableWebSocket; |
| | | //import org.springframework.web.socket.config.annotation.WebSocketConfigurer; |
| | | //import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; |
| | | // |
| | | ///** |
| | | // * Spring WebSocket 配置 |
| | | // * 注册 /websocket 端点,共用 Spring Boot HTTP 端口 |
| | | // */ |
| | | //@Configuration |
| | | //@EnableWebSocket |
| | | //public class WebSocketConfig implements WebSocketConfigurer { |
| | | // |
| | | // @Autowired |
| | | // private PushMessageWebSocketHandler pushMessageWebSocketHandler; |
| | | // |
| | | // @Override |
| | | // public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { |
| | | // registry.addHandler(pushMessageWebSocketHandler, "/websocket") |
| | | // // 允许所有跨域来源,生产环境可按需收紧 |
| | | // .setAllowedOrigins("*"); |
| | | // } |
| | | //} |
| New file |
| | |
| | | package com.xiaopeng.pushMessage.webSocket.config; |
| | | |
| | | import com.xiaopeng.pushMessage.webSocket.handler.WebSocketTextFrameHandler; |
| | | import io.netty.bootstrap.ServerBootstrap; |
| | | import io.netty.channel.ChannelFuture; |
| | | import io.netty.channel.ChannelInitializer; |
| | | import io.netty.channel.ChannelOption; |
| | | import io.netty.channel.ChannelPipeline; |
| | | import io.netty.channel.nio.NioEventLoopGroup; |
| | | import io.netty.channel.socket.SocketChannel; |
| | | import io.netty.channel.socket.nio.NioServerSocketChannel; |
| | | import io.netty.handler.codec.http.HttpObjectAggregator; |
| | | import io.netty.handler.codec.http.HttpServerCodec; |
| | | import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; |
| | | import io.netty.handler.logging.LogLevel; |
| | | import io.netty.handler.logging.LoggingHandler; |
| | | import io.netty.handler.stream.ChunkedWriteHandler; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | @Component |
| | | @Slf4j |
| | | public class WebSocketServer { |
| | | private final int port; |
| | | |
| | | @Autowired |
| | | private WebSocketTextFrameHandler webSocketTextFrameHandler; |
| | | |
| | | public WebSocketServer() { |
| | | this.port = 8888; // 默认端口 |
| | | } |
| | | |
| | | public WebSocketServer(int port) { |
| | | this.port = port; |
| | | } |
| | | |
| | | public void run() throws InterruptedException { |
| | | NioEventLoopGroup bossGroup = new NioEventLoopGroup(1); |
| | | NioEventLoopGroup workerGroup = new NioEventLoopGroup(); |
| | | try { |
| | | ServerBootstrap serverBootstrap = new ServerBootstrap(); |
| | | serverBootstrap.group(bossGroup, workerGroup) |
| | | .channel(NioServerSocketChannel.class) |
| | | .option(ChannelOption.SO_BACKLOG, 128) |
| | | .childOption(ChannelOption.SO_KEEPALIVE, true) |
| | | .handler(new LoggingHandler(LogLevel.INFO)) // 在BossGroup中增加一个日志处理器 日志级别为INFO |
| | | .childHandler(new ChannelInitializer<SocketChannel>() { |
| | | @Override |
| | | protected void initChannel(SocketChannel ch) throws Exception { |
| | | ChannelPipeline pipeline = ch.pipeline(); |
| | | // 因为是基于Http协议的所以采用Http的编解码器 |
| | | pipeline.addLast(new HttpServerCodec()); |
| | | // 是以块的方式写, 添加ChunkedWriteHandler(分块写入处理程序) |
| | | pipeline.addLast(new ChunkedWriteHandler()); |
| | | /* |
| | | * http 数据在传输过程中是分段的 http Object aggregator 就是可以将多个段聚合 |
| | | * 这就是为什么 当浏览器发送大量数据时, 就会出现多次http请求 |
| | | * 参数为 : 最大内容长度 |
| | | */ |
| | | pipeline.addLast(new HttpObjectAggregator(8192)); |
| | | /* |
| | | * 对应WebSocket 他的数据时以桢(frame) 形式传递 |
| | | * 可以看到WebSocketFrame下面有6个子类 |
| | | * 浏览器请求时: ws://localhost:7000/xxx 请求的url |
| | | * 核心功能是将http协议升级为ws协议 保持长链接 |
| | | */ |
| | | pipeline.addLast(new WebSocketServerProtocolHandler("/websocket")); |
| | | // 自定义Handler, 处理业务逻辑 |
| | | // 使用@Sharable注解的Handler,可以被多个连接共享 |
| | | pipeline.addLast(webSocketTextFrameHandler); |
| | | } |
| | | }); |
| | | log.info("WebSocket服务器启动中,端口: {}", port); |
| | | ChannelFuture sync = serverBootstrap.bind(port).sync(); |
| | | sync.channel().closeFuture().sync(); |
| | | } catch (InterruptedException e) { |
| | | log.error("WebSocket服务器启动失败", e); |
| | | } finally { |
| | | bossGroup.shutdownGracefully(); |
| | | workerGroup.shutdownGracefully(); |
| | | } |
| | | |
| | | } |
| | | |
| | | // public static void main(String[] args) throws InterruptedException { |
| | | // new WebSocketServer(7000).run(); |
| | | // } |
| | | } |
| New file |
| | |
| | | package com.xiaopeng.pushMessage.webSocket.config; |
| | | |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.boot.CommandLineRunner; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | /** |
| | | * WebSocket服务器配置类 |
| | | * 在Spring Boot应用启动时自动启动WebSocket服务器 |
| | | */ |
| | | @Component |
| | | @Slf4j |
| | | public class WebSocketServerConfig implements CommandLineRunner { |
| | | |
| | | @Autowired |
| | | private WebSocketServer webSocketServer; |
| | | |
| | | @Override |
| | | public void run(String... args) throws Exception { |
| | | // 在新线程中启动WebSocket服务器,避免阻塞主线程 |
| | | new Thread(() -> { |
| | | try { |
| | | log.info("正在启动WebSocket服务器..."); |
| | | webSocketServer.run(); |
| | | } catch (InterruptedException e) { |
| | | log.error("WebSocket服务器启动失败", e); |
| | | Thread.currentThread().interrupt(); |
| | | } |
| | | }, "WebSocket-Server-Thread").start(); |
| | | } |
| | | } |
| | |
| | | package com.xiaopeng.pushMessage.webSocket.controller; |
| | | |
| | | import com.alibaba.fastjson.JSONObject; |
| | | import com.xiaopeng.api.dto.pushMessage.SendMessageDTO; |
| | | import com.xiaopeng.common.core.domain.R; |
| | | import com.xiaopeng.common.core.web.controller.BaseController; |
| | |
| | | * @return 操作结果 |
| | | */ |
| | | @ApiOperation(value = "发送订单状态") |
| | | @PostMapping("/logan/inner/websocket/order/sendStatus") |
| | | @PostMapping("/inner/websocket/order/sendStatus") |
| | | public R<String> sendStatus(@RequestBody SendMessageDTO dto) { |
| | | try { |
| | | String message = dto.getMessage(); |
| | |
| | | * @return 操作结果 |
| | | */ |
| | | @ApiOperation(value = "发送车辆路线、时间") |
| | | @PostMapping("/logan/inner/websocket/order/sendRoute") |
| | | @PostMapping("/inner/websocket/order/sendRoute") |
| | | public R<String> sendRoute(@RequestBody SendMessageDTO dto) { |
| | | try { |
| | | String message = dto.getMessage(); |
| | |
| | | * @return 操作结果 |
| | | */ |
| | | @ApiOperation(value = "发送车辆实时定位") |
| | | @PostMapping("/logan/inner/websocket/order/sendVehicleLocation") |
| | | @PostMapping("/inner/websocket/order/sendVehicleLocation") |
| | | public R<String> sendVehicleLocation(@RequestBody SendMessageDTO dto) { |
| | | try { |
| | | String message = dto.getMessage(); |
| | |
| | | * @return 操作结果 |
| | | */ |
| | | @ApiOperation(value = "发送是否开启倒计时") |
| | | @PostMapping("/logan/inner/websocket/order/sendOpenTrunk") |
| | | @PostMapping("/inner/websocket/order/sendOpenTrunk") |
| | | public R<String> sendOpenTrunk(@RequestBody SendMessageDTO dto) { |
| | | try { |
| | | String message = dto.getMessage(); |
| | |
| | | package com.xiaopeng.pushMessage.webSocket.handler; |
| | | |
| | | import com.alibaba.fastjson.JSON; |
| | | import com.alibaba.fastjson.JSONObject; |
| | | import com.fasterxml.jackson.databind.DeserializationFeature; |
| | | import com.fasterxml.jackson.databind.ObjectMapper; |
| | | import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; |
| | | import com.xiaopeng.api.dto.account.XpOperationsUserPositionDTO; |
| | | import com.xiaopeng.pushMessage.webSocket.service.WebSocketUserConnectionManager; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.data.mongodb.core.MongoTemplate; |
| | | import org.springframework.data.mongodb.core.query.Criteria; |
| | | import org.springframework.data.mongodb.core.query.Query; |
| | | import org.springframework.data.mongodb.core.query.Update; |
| | | import org.springframework.stereotype.Component; |
| | | import org.springframework.util.StringUtils; |
| | | import org.springframework.web.socket.CloseStatus; |
| | | import org.springframework.web.socket.TextMessage; |
| | | import org.springframework.web.socket.WebSocketSession; |
| | | import org.springframework.web.socket.handler.TextWebSocketHandler; |
| | | |
| | | import javax.annotation.PostConstruct; |
| | | import java.time.LocalDateTime; |
| | | import java.util.UUID; |
| | | |
| | | /** |
| | | * Spring WebSocket 消息处理器 |
| | | * 路径: /websocket,共用 Spring Boot HTTP 端口 |
| | | * 替代原 Netty 独立端口方案 |
| | | */ |
| | | @Component |
| | | @Slf4j |
| | | public class PushMessageWebSocketHandler extends TextWebSocketHandler { |
| | | |
| | | @Autowired |
| | | private WebSocketUserConnectionManager userConnectionManager; |
| | | |
| | | @Autowired |
| | | private WebSocketAuthHandler authHandler; |
| | | |
| | | private ObjectMapper om; |
| | | |
| | | @PostConstruct |
| | | void setupObjectMapper() { |
| | | // 允许对象忽略json中不存在的属性 |
| | | om = new ObjectMapper(); |
| | | om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); |
| | | om.registerModule(new JavaTimeModule()); |
| | | } |
| | | |
| | | // ==================== 连接生命周期 ==================== |
| | | |
| | | @Override |
| | | public void afterConnectionEstablished(WebSocketSession session) throws Exception { |
| | | log.info("WebSocket 连接建立,sessionId: {}", session.getId()); |
| | | } |
| | | |
| | | @Override |
| | | public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { |
| | | log.info("WebSocket 连接关闭,sessionId: {}, status: {}", session.getId(), status); |
| | | userConnectionManager.removeUserConnection(session); |
| | | } |
| | | |
| | | @Override |
| | | public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { |
| | | log.error("WebSocket 传输异常,sessionId: {}, error: {}", session.getId(), exception.getMessage(), exception); |
| | | if (session.isOpen()) { |
| | | session.close(CloseStatus.SERVER_ERROR); |
| | | } |
| | | } |
| | | |
| | | // ==================== 消息处理 ==================== |
| | | |
| | | @Override |
| | | protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { |
| | | String payload = message.getPayload(); |
| | | log.info("[服务器] 收到消息 -> {}", payload); |
| | | |
| | | try { |
| | | if (payload.startsWith("pos:")) { |
| | | if (log.isDebugEnabled()) |
| | | log.debug("收到运维人员位置上报消息:{}",payload); |
| | | XpOperationsUserPositionDTO xpOperationsUserPositionDTO = om.readValue(payload.substring(4), XpOperationsUserPositionDTO.class); |
| | | saveToMongo(xpOperationsUserPositionDTO); |
| | | } |
| | | JSONObject messageObj = JSON.parseObject(payload); |
| | | String messageType = messageObj.getString("type"); |
| | | |
| | | if ("auth".equals(messageType)) { |
| | | handleUserAuth(session, messageObj); |
| | | } else if ("ping".equals(messageType)) { |
| | | handlePing(session); |
| | | } else { |
| | | handleBusinessMessage(session, messageObj); |
| | | } |
| | | } catch (Exception e) { |
| | | log.debug("消息非 JSON 格式,按普通文本处理: {}", payload); |
| | | sendText(session, "服务器时间: " + LocalDateTime.now() + "->" + payload); |
| | | } |
| | | } |
| | | |
| | | @Autowired |
| | | private MongoTemplate mongoTemplate; |
| | | private void saveToMongo(XpOperationsUserPositionDTO dto) { |
| | | XpOperationsUserPositionDTO vehiclePosition = mongoTemplate.findOne(new Query(Criteria.where("id").is(dto.getId())), XpOperationsUserPositionDTO.class); |
| | | if (null == vehiclePosition) { |
| | | mongoTemplate.save(dto); |
| | | }else{ |
| | | Update update = new Update() |
| | | .set("id",dto.getId()) |
| | | .set("latitude", dto.getLatitude()) |
| | | .set("latitude", dto.getLongitude()); |
| | | mongoTemplate.updateFirst(new Query(Criteria.where("id").is(dto.getId())), update, XpOperationsUserPositionDTO.class); |
| | | } |
| | | } |
| | | |
| | | // ==================== 业务处理 ==================== |
| | | |
| | | /** |
| | | * 处理用户认证消息 |
| | | */ |
| | | private void handleUserAuth(WebSocketSession session, JSONObject messageObj) { |
| | | try { |
| | | WebSocketAuthHandler.UserAuthInfo authInfo = authHandler.extractAuthInfo(messageObj); |
| | | String appUserId = authInfo.getAppUserId(); |
| | | String token = authInfo.getToken(); |
| | | |
| | | if (!StringUtils.hasText(appUserId)) { |
| | | sendAuthFailure(session, "用户ID不能为空"); |
| | | return; |
| | | } |
| | | |
| | | WebSocketAuthHandler.AuthResult authResult = authHandler.authenticateUser(appUserId, token); |
| | | if (authResult.isSuccess()) { |
| | | userConnectionManager.addUserConnection(appUserId, session); |
| | | |
| | | // TODO |
| | | String uuid = UUID.randomUUID().toString(); |
| | | JSONObject response = new JSONObject(); |
| | | response.put("type", "auth_success"); |
| | | response.put("appUserId", appUserId); |
| | | response.put("nickName", uuid); |
| | | response.put("message", "认证成功"); |
| | | response.put("timestamp", LocalDateTime.now().toString()); |
| | | |
| | | sendText(session, response.toJSONString()); |
| | | log.info("用户 {} ({}) 认证成功,sessionId: {}", appUserId, uuid, session.getId()); |
| | | } else { |
| | | sendAuthFailure(session, authResult.getMessage()); |
| | | log.warn("用户 {} 认证失败: {}, sessionId: {}", appUserId, authResult.getMessage(), session.getId()); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("处理用户认证时发生错误", e); |
| | | sendAuthFailure(session, "认证过程中发生错误: " + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | private void sendAuthFailure(WebSocketSession session, String message) { |
| | | JSONObject response = new JSONObject(); |
| | | response.put("type", "auth_failed"); |
| | | response.put("message", message); |
| | | response.put("timestamp", LocalDateTime.now().toString()); |
| | | sendText(session, response.toJSONString()); |
| | | } |
| | | |
| | | /** |
| | | * 处理心跳消息 |
| | | */ |
| | | private void handlePing(WebSocketSession session) { |
| | | String appUserId = userConnectionManager.getUserIdBySessionId(session.getId()); |
| | | |
| | | JSONObject response = new JSONObject(); |
| | | response.put("type", "pong"); |
| | | response.put("appUserId", appUserId); |
| | | response.put("timestamp", LocalDateTime.now().toString()); |
| | | log.info("心跳用户id:{}", appUserId); |
| | | |
| | | sendText(session, response.toJSONString()); |
| | | } |
| | | |
| | | /** |
| | | * 处理业务消息 |
| | | */ |
| | | private void handleBusinessMessage(WebSocketSession session, JSONObject messageObj) { |
| | | String appUserId = userConnectionManager.getUserIdBySessionId(session.getId()); |
| | | |
| | | if (appUserId != null) { |
| | | log.info("收到用户 {} 的业务消息: {}", appUserId, messageObj.toJSONString()); |
| | | |
| | | JSONObject response = new JSONObject(); |
| | | response.put("type", "message_response"); |
| | | response.put("appUserId", appUserId); |
| | | response.put("originalMessage", messageObj); |
| | | response.put("serverTime", LocalDateTime.now().toString()); |
| | | |
| | | sendText(session, response.toJSONString()); |
| | | } else { |
| | | log.warn("收到未认证连接的业务消息: {}", messageObj.toJSONString()); |
| | | |
| | | JSONObject response = new JSONObject(); |
| | | response.put("type", "auth_required"); |
| | | response.put("message", "请先进行用户认证"); |
| | | response.put("timestamp", LocalDateTime.now().toString()); |
| | | |
| | | sendText(session, response.toJSONString()); |
| | | } |
| | | } |
| | | |
| | | // ==================== 工具方法 ==================== |
| | | |
| | | /** |
| | | * 发送文本消息(线程安全) |
| | | */ |
| | | private void sendText(WebSocketSession session, String text) { |
| | | try { |
| | | if (session.isOpen()) { |
| | | synchronized (session) { |
| | | session.sendMessage(new TextMessage(text)); |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("发送消息失败,sessionId: {}, error: {}", session.getId(), e.getMessage()); |
| | | } |
| | | } |
| | | } |
| | | //package com.xiaopeng.pushMessage.webSocket.handler; |
| | | // |
| | | //import com.alibaba.fastjson.JSON; |
| | | //import com.alibaba.fastjson.JSONObject; |
| | | //import com.xiaopeng.pushMessage.webSocket.service.WebSocketUserConnectionManager; |
| | | //import lombok.extern.slf4j.Slf4j; |
| | | //import org.springframework.beans.factory.annotation.Autowired; |
| | | //import org.springframework.stereotype.Component; |
| | | //import org.springframework.util.StringUtils; |
| | | //import org.springframework.web.socket.CloseStatus; |
| | | //import org.springframework.web.socket.TextMessage; |
| | | //import org.springframework.web.socket.WebSocketSession; |
| | | //import org.springframework.web.socket.handler.TextWebSocketHandler; |
| | | // |
| | | //import java.time.LocalDateTime; |
| | | //import java.util.UUID; |
| | | // |
| | | ///** |
| | | // * Spring WebSocket 消息处理器 |
| | | // * 路径: /websocket,共用 Spring Boot HTTP 端口 |
| | | // * 替代原 Netty 独立端口方案 |
| | | // */ |
| | | //@Component |
| | | //@Slf4j |
| | | //public class PushMessageWebSocketHandler extends TextWebSocketHandler { |
| | | // |
| | | // @Autowired |
| | | // private WebSocketUserConnectionManager userConnectionManager; |
| | | // |
| | | // @Autowired |
| | | // private WebSocketAuthHandler authHandler; |
| | | // |
| | | // // ==================== 连接生命周期 ==================== |
| | | // |
| | | // @Override |
| | | // public void afterConnectionEstablished(WebSocketSession session) throws Exception { |
| | | // log.info("WebSocket 连接建立,sessionId: {}", session.getId()); |
| | | // } |
| | | // |
| | | // @Override |
| | | // public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { |
| | | // log.info("WebSocket 连接关闭,sessionId: {}, status: {}", session.getId(), status); |
| | | // userConnectionManager.removeUserConnection(session); |
| | | // } |
| | | // |
| | | // @Override |
| | | // public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { |
| | | // log.error("WebSocket 传输异常,sessionId: {}, error: {}", session.getId(), exception.getMessage(), exception); |
| | | // if (session.isOpen()) { |
| | | // session.close(CloseStatus.SERVER_ERROR); |
| | | // } |
| | | // } |
| | | // |
| | | // // ==================== 消息处理 ==================== |
| | | // |
| | | // @Override |
| | | // protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { |
| | | // String payload = message.getPayload(); |
| | | // log.info("[服务器] 收到消息 -> {}", payload); |
| | | // |
| | | // try { |
| | | // JSONObject messageObj = JSON.parseObject(payload); |
| | | // String messageType = messageObj.getString("type"); |
| | | // |
| | | // if ("auth".equals(messageType)) { |
| | | // handleUserAuth(session, messageObj); |
| | | // } else if ("ping".equals(messageType)) { |
| | | // handlePing(session); |
| | | // } else { |
| | | // handleBusinessMessage(session, messageObj); |
| | | // } |
| | | // } catch (Exception e) { |
| | | // log.debug("消息非 JSON 格式,按普通文本处理: {}", payload); |
| | | // sendText(session, "服务器时间: " + LocalDateTime.now() + "->" + payload); |
| | | // } |
| | | // } |
| | | // |
| | | // // ==================== 业务处理 ==================== |
| | | // |
| | | // /** |
| | | // * 处理用户认证消息 |
| | | // */ |
| | | // private void handleUserAuth(WebSocketSession session, JSONObject messageObj) { |
| | | // try { |
| | | // WebSocketAuthHandler.UserAuthInfo authInfo = authHandler.extractAuthInfo(messageObj); |
| | | // String appUserId = authInfo.getAppUserId(); |
| | | // String token = authInfo.getToken(); |
| | | // |
| | | // if (!StringUtils.hasText(appUserId)) { |
| | | // sendAuthFailure(session, "用户ID不能为空"); |
| | | // return; |
| | | // } |
| | | // |
| | | // WebSocketAuthHandler.AuthResult authResult = authHandler.authenticateUser(appUserId, token); |
| | | // if (authResult.isSuccess()) { |
| | | // userConnectionManager.addUserConnection(appUserId, session); |
| | | // |
| | | // // TODO |
| | | // String uuid = UUID.randomUUID().toString(); |
| | | // JSONObject response = new JSONObject(); |
| | | // response.put("type", "auth_success"); |
| | | // response.put("appUserId", appUserId); |
| | | // response.put("nickName", uuid); |
| | | // response.put("message", "认证成功"); |
| | | // response.put("timestamp", LocalDateTime.now().toString()); |
| | | // |
| | | // sendText(session, response.toJSONString()); |
| | | // log.info("用户 {} ({}) 认证成功,sessionId: {}", appUserId, uuid, session.getId()); |
| | | // } else { |
| | | // sendAuthFailure(session, authResult.getMessage()); |
| | | // log.warn("用户 {} 认证失败: {}, sessionId: {}", appUserId, authResult.getMessage(), session.getId()); |
| | | // } |
| | | // } catch (Exception e) { |
| | | // log.error("处理用户认证时发生错误", e); |
| | | // sendAuthFailure(session, "认证过程中发生错误: " + e.getMessage()); |
| | | // } |
| | | // } |
| | | // |
| | | // private void sendAuthFailure(WebSocketSession session, String message) { |
| | | // JSONObject response = new JSONObject(); |
| | | // response.put("type", "auth_failed"); |
| | | // response.put("message", message); |
| | | // response.put("timestamp", LocalDateTime.now().toString()); |
| | | // sendText(session, response.toJSONString()); |
| | | // } |
| | | // |
| | | // /** |
| | | // * 处理心跳消息 |
| | | // */ |
| | | // private void handlePing(WebSocketSession session) { |
| | | // String appUserId = userConnectionManager.getUserIdBySessionId(session.getId()); |
| | | // |
| | | // JSONObject response = new JSONObject(); |
| | | // response.put("type", "pong"); |
| | | // response.put("appUserId", appUserId); |
| | | // response.put("timestamp", LocalDateTime.now().toString()); |
| | | // log.info("心跳用户id:{}", appUserId); |
| | | // |
| | | // sendText(session, response.toJSONString()); |
| | | // } |
| | | // |
| | | // /** |
| | | // * 处理业务消息 |
| | | // */ |
| | | // private void handleBusinessMessage(WebSocketSession session, JSONObject messageObj) { |
| | | // String appUserId = userConnectionManager.getUserIdBySessionId(session.getId()); |
| | | // |
| | | // if (appUserId != null) { |
| | | // log.info("收到用户 {} 的业务消息: {}", appUserId, messageObj.toJSONString()); |
| | | // |
| | | // JSONObject response = new JSONObject(); |
| | | // response.put("type", "message_response"); |
| | | // response.put("appUserId", appUserId); |
| | | // response.put("originalMessage", messageObj); |
| | | // response.put("serverTime", LocalDateTime.now().toString()); |
| | | // |
| | | // sendText(session, response.toJSONString()); |
| | | // } else { |
| | | // log.warn("收到未认证连接的业务消息: {}", messageObj.toJSONString()); |
| | | // |
| | | // JSONObject response = new JSONObject(); |
| | | // response.put("type", "auth_required"); |
| | | // response.put("message", "请先进行用户认证"); |
| | | // response.put("timestamp", LocalDateTime.now().toString()); |
| | | // |
| | | // sendText(session, response.toJSONString()); |
| | | // } |
| | | // } |
| | | // |
| | | // // ==================== 工具方法 ==================== |
| | | // |
| | | // /** |
| | | // * 发送文本消息(线程安全) |
| | | // */ |
| | | // private void sendText(WebSocketSession session, String text) { |
| | | // try { |
| | | // if (session.isOpen()) { |
| | | // synchronized (session) { |
| | | // session.sendMessage(new TextMessage(text)); |
| | | // } |
| | | // } |
| | | // } catch (Exception e) { |
| | | // log.error("发送消息失败,sessionId: {}, error: {}", session.getId(), e.getMessage()); |
| | | // } |
| | | // } |
| | | //} |
| | |
| | | import com.xiaopeng.api.dto.account.UserResourcesDto; |
| | | import com.xiaopeng.api.feignClient.account.UserResourcesClient; |
| | | import com.xiaopeng.api.feignClient.account.XpAppUserClient; |
| | | import com.xiaopeng.api.model.account.XpAppUser; |
| | | import com.xiaopeng.api.vo.account.LoginOperationUser; |
| | | import com.xiaopeng.common.core.constant.CacheConstants; |
| | | import com.xiaopeng.common.core.constant.Constants; |
| | |
| | | import io.jsonwebtoken.Jwts; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.beans.factory.annotation.Value; |
| | | import org.springframework.stereotype.Component; |
| | | import org.springframework.util.StringUtils; |
| | | |
| | | import java.util.Map; |
| | | import java.util.Objects; |
| | | |
| | | /** |
| | | * WebSocket认证处理器 |
| | |
| | | package com.xiaopeng.pushMessage.webSocket.handler; |
| | | |
| | | //import com.alibaba.fastjson.JSON; |
| | | //import com.alibaba.fastjson.JSONObject; |
| | | //import com.xiaopeng.pushMessage.webSocket.service.WebSocketUserConnectionManager; |
| | | //import io.netty.channel.ChannelHandler; |
| | | //import io.netty.channel.ChannelHandlerContext; |
| | | //import io.netty.channel.SimpleChannelInboundHandler; |
| | | //import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; |
| | | //import lombok.extern.slf4j.Slf4j; |
| | | //import org.springframework.beans.factory.annotation.Autowired; |
| | | //import org.springframework.stereotype.Component; |
| | | //import org.springframework.util.StringUtils; |
| | | // |
| | | //import java.time.LocalDateTime; |
| | | //import java.util.Objects; |
| | | //import java.util.UUID; |
| | | // |
| | | ///** |
| | | // * TextWebSocketFrame 表示一个文本桢 |
| | | // * 使用@Sharable注解使其可以被多个连接共享 |
| | | // * 支持基于AppUser的连接管理 |
| | | // */ |
| | | //@Component |
| | | //@Slf4j |
| | | //@ChannelHandler.Sharable |
| | | //public class WebSocketTextFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> { |
| | | // |
| | | // |
| | | // @Autowired |
| | | // private WebSocketUserConnectionManager userConnectionManager; |
| | | // |
| | | // @Autowired |
| | | // private WebSocketAuthHandler authHandler; |
| | | // |
| | | // @Override |
| | | // protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception { |
| | | // String message = msg.text(); |
| | | // log.info("[服务器] : 收到消息 -> {}", message); |
| | | // |
| | | // try { |
| | | // // 尝试解析消息,检查是否包含用户认证信息 |
| | | // JSONObject messageObj = JSON.parseObject(message); |
| | | // String messageType = messageObj.getString("type"); |
| | | // |
| | | // if ("auth".equals(messageType)) { |
| | | // // 处理用户认证消息 |
| | | // handleUserAuth(ctx, messageObj); |
| | | // } else if ("ping".equals(messageType)) { |
| | | // // 处理心跳消息 |
| | | // handlePing(ctx, messageObj); |
| | | // } else { |
| | | // // 处理普通业务消息 |
| | | // handleBusinessMessage(ctx, messageObj); |
| | | // } |
| | | // } catch (Exception e) { |
| | | // // 如果不是JSON格式,按普通文本处理 |
| | | // log.debug("消息不是JSON格式,按普通文本处理: {}", message); |
| | | // ctx.channel().writeAndFlush(new TextWebSocketFrame("服务器时间: " + LocalDateTime.now() + "->" + message)); |
| | | // } |
| | | // } |
| | | // |
| | | // /** |
| | | // * 处理用户认证消息 |
| | | // */ |
| | | // private void handleUserAuth(ChannelHandlerContext ctx, JSONObject messageObj) { |
| | | // try { |
| | | // // 提取认证信息 |
| | | // WebSocketAuthHandler.UserAuthInfo authInfo = authHandler.extractAuthInfo(messageObj); |
| | | // String appUserId = authInfo.getAppUserId(); |
| | | // String token = authInfo.getToken(); |
| | | // |
| | | // if (!StringUtils.hasText(appUserId)) { |
| | | // sendAuthFailureResponse(ctx, "用户ID不能为空"); |
| | | // return; |
| | | // } |
| | | // |
| | | // // 进行用户认证 |
| | | // WebSocketAuthHandler.AuthResult authResult = authHandler.authenticateUser(appUserId, token); |
| | | // if (authResult.isSuccess()) { |
| | | // // 认证成功,将连接与用户关联 |
| | | // userConnectionManager.addUserConnection(appUserId, ctx.channel()); |
| | | // |
| | | // // 发送认证成功响应 |
| | | // JSONObject response = new JSONObject(); |
| | | // response.put("type", "auth_success"); |
| | | // response.put("appUserId", appUserId); |
| | | // // TODO |
| | | // String uuid = UUID.randomUUID().toString(); |
| | | // response.put("nickName", uuid); |
| | | //// response.put("nickName", authResult.getAppUser().getNickName()); |
| | | // response.put("message", "认证成功"); |
| | | // response.put("timestamp", LocalDateTime.now().toString()); |
| | | // |
| | | // ctx.channel().writeAndFlush(new TextWebSocketFrame(response.toJSONString())); |
| | | // log.info("用户 {} ({}) 认证成功,ChannelId: {}", |
| | | // appUserId, uuid, ctx.channel().id().asLongText()); |
| | | // } else { |
| | | // // 认证失败 |
| | | // sendAuthFailureResponse(ctx, authResult.getMessage()); |
| | | // log.warn("用户 {} 认证失败: {}, ChannelId: {}", |
| | | // appUserId, authResult.getMessage(), ctx.channel().id().asLongText()); |
| | | // } |
| | | // } catch (Exception e) { |
| | | // log.error("处理用户认证时发生错误", e); |
| | | // sendAuthFailureResponse(ctx, "认证过程中发生错误: " + e.getMessage()); |
| | | // } |
| | | // } |
| | | // |
| | | // /** |
| | | // * 发送认证失败响应 |
| | | // */ |
| | | // private void sendAuthFailureResponse(ChannelHandlerContext ctx, String message) { |
| | | // JSONObject response = new JSONObject(); |
| | | // response.put("type", "auth_failed"); |
| | | // response.put("message", message); |
| | | // response.put("timestamp", LocalDateTime.now().toString()); |
| | | // |
| | | // ctx.channel().writeAndFlush(new TextWebSocketFrame(response.toJSONString())); |
| | | // } |
| | | // |
| | | // /** |
| | | // * 处理心跳消息 |
| | | // */ |
| | | // private void handlePing(ChannelHandlerContext ctx, JSONObject messageObj) { |
| | | // String appUserId = userConnectionManager.getUserIdByChannelId(ctx.channel().id().asLongText()); |
| | | // |
| | | // JSONObject response = new JSONObject(); |
| | | // response.put("type", "pong"); |
| | | // log.info("心跳用户id:{}",appUserId); |
| | | // response.put("appUserId", appUserId); |
| | | // response.put("timestamp", LocalDateTime.now().toString()); |
| | | // |
| | | // ctx.channel().writeAndFlush(new TextWebSocketFrame(response.toJSONString())); |
| | | // } |
| | | // |
| | | // /** |
| | | // * 处理业务消息 |
| | | // */ |
| | | // private void handleBusinessMessage(ChannelHandlerContext ctx, JSONObject messageObj) { |
| | | // String appUserId = userConnectionManager.getUserIdByChannelId(ctx.channel().id().asLongText()); |
| | | // |
| | | // if (appUserId != null) { |
| | | // log.info("收到用户 {} 的业务消息: {}", appUserId, messageObj.toJSONString()); |
| | | // |
| | | // // 回复消息 |
| | | // JSONObject response = new JSONObject(); |
| | | // response.put("type", "message_response"); |
| | | // response.put("appUserId", appUserId); |
| | | // response.put("originalMessage", messageObj); |
| | | // response.put("serverTime", LocalDateTime.now().toString()); |
| | | // |
| | | // ctx.channel().writeAndFlush(new TextWebSocketFrame(response.toJSONString())); |
| | | // } else { |
| | | // log.warn("收到未认证连接的业务消息: {}", messageObj.toJSONString()); |
| | | // |
| | | // // 要求先认证 |
| | | // JSONObject response = new JSONObject(); |
| | | // response.put("type", "auth_required"); |
| | | // response.put("message", "请先进行用户认证"); |
| | | // response.put("timestamp", LocalDateTime.now().toString()); |
| | | // |
| | | // ctx.channel().writeAndFlush(new TextWebSocketFrame(response.toJSONString())); |
| | | // } |
| | | // } |
| | | // |
| | | // @Override |
| | | // public void handlerAdded(ChannelHandlerContext ctx) throws Exception { |
| | | // // id 表示唯一的值 LongText是唯一的 |
| | | // log.info("handlerAdded 被调用: {}", ctx.channel().id().asLongText()); |
| | | // // shortText 可能会重复 |
| | | // log.info("handlerAdded 被调用: {}", ctx.channel().id().asShortText()); |
| | | // |
| | | // // 注意:用户连接管理在认证时添加,这里不添加 |
| | | // } |
| | | // |
| | | // @Override |
| | | // public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { |
| | | // // id 表示唯一的值 LongText是唯一的 |
| | | // log.info("handlerRemoved 被调用: {}", ctx.channel().id().asLongText()); |
| | | // // shortText 可能会重复 |
| | | // log.info("handlerRemoved 被调用: {}", ctx.channel().id().asShortText()); |
| | | // |
| | | // // 从用户连接管理器中移除连接 |
| | | // if (userConnectionManager != null) { |
| | | // userConnectionManager.removeUserConnection(ctx.channel()); |
| | | // } |
| | | // } |
| | | // |
| | | // @Override |
| | | // public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { |
| | | // log.error("WebSocket连接异常: {}", cause.getMessage(), cause); |
| | | // ctx.channel().close(); |
| | | // } |
| | | //} |
| | | import com.alibaba.fastjson.JSON; |
| | | import com.alibaba.fastjson.JSONObject; |
| | | import com.xiaopeng.pushMessage.webSocket.service.WebSocketUserConnectionManager; |
| | | import io.netty.channel.ChannelHandler; |
| | | import io.netty.channel.ChannelHandlerContext; |
| | | import io.netty.channel.SimpleChannelInboundHandler; |
| | | import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.stereotype.Component; |
| | | import org.springframework.util.StringUtils; |
| | | |
| | | import java.time.LocalDateTime; |
| | | import java.util.Objects; |
| | | import java.util.UUID; |
| | | |
| | | /** |
| | | * TextWebSocketFrame 表示一个文本桢 |
| | | * 使用@Sharable注解使其可以被多个连接共享 |
| | | * 支持基于AppUser的连接管理 |
| | | */ |
| | | @Component |
| | | @Slf4j |
| | | @ChannelHandler.Sharable |
| | | public class WebSocketTextFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> { |
| | | |
| | | |
| | | @Autowired |
| | | private WebSocketUserConnectionManager userConnectionManager; |
| | | |
| | | @Autowired |
| | | private WebSocketAuthHandler authHandler; |
| | | |
| | | @Override |
| | | protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception { |
| | | String message = msg.text(); |
| | | log.info("[服务器] : 收到消息 -> {}", message); |
| | | |
| | | try { |
| | | // 尝试解析消息,检查是否包含用户认证信息 |
| | | JSONObject messageObj = JSON.parseObject(message); |
| | | String messageType = messageObj.getString("type"); |
| | | |
| | | if ("auth".equals(messageType)) { |
| | | // 处理用户认证消息 |
| | | handleUserAuth(ctx, messageObj); |
| | | } else if ("ping".equals(messageType)) { |
| | | // 处理心跳消息 |
| | | handlePing(ctx, messageObj); |
| | | } else { |
| | | // 处理普通业务消息 |
| | | handleBusinessMessage(ctx, messageObj); |
| | | } |
| | | } catch (Exception e) { |
| | | // 如果不是JSON格式,按普通文本处理 |
| | | log.debug("消息不是JSON格式,按普通文本处理: {}", message); |
| | | ctx.channel().writeAndFlush(new TextWebSocketFrame("服务器时间: " + LocalDateTime.now() + "->" + message)); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 处理用户认证消息 |
| | | */ |
| | | private void handleUserAuth(ChannelHandlerContext ctx, JSONObject messageObj) { |
| | | try { |
| | | // 提取认证信息 |
| | | WebSocketAuthHandler.UserAuthInfo authInfo = authHandler.extractAuthInfo(messageObj); |
| | | String appUserId = authInfo.getAppUserId(); |
| | | String token = authInfo.getToken(); |
| | | |
| | | if (!StringUtils.hasText(appUserId)) { |
| | | sendAuthFailureResponse(ctx, "用户ID不能为空"); |
| | | return; |
| | | } |
| | | |
| | | // 进行用户认证 |
| | | WebSocketAuthHandler.AuthResult authResult = authHandler.authenticateUser(appUserId, token); |
| | | if (authResult.isSuccess()) { |
| | | // 认证成功,将连接与用户关联 |
| | | userConnectionManager.addUserConnection(appUserId, ctx.channel()); |
| | | |
| | | // 发送认证成功响应 |
| | | JSONObject response = new JSONObject(); |
| | | response.put("type", "auth_success"); |
| | | response.put("appUserId", appUserId); |
| | | // TODO |
| | | String uuid = UUID.randomUUID().toString(); |
| | | response.put("nickName", uuid); |
| | | // response.put("nickName", authResult.getAppUser().getNickName()); |
| | | response.put("message", "认证成功"); |
| | | response.put("timestamp", LocalDateTime.now().toString()); |
| | | |
| | | ctx.channel().writeAndFlush(new TextWebSocketFrame(response.toJSONString())); |
| | | log.info("用户 {} ({}) 认证成功,ChannelId: {}", |
| | | appUserId, uuid, ctx.channel().id().asLongText()); |
| | | } else { |
| | | // 认证失败 |
| | | sendAuthFailureResponse(ctx, authResult.getMessage()); |
| | | log.warn("用户 {} 认证失败: {}, ChannelId: {}", |
| | | appUserId, authResult.getMessage(), ctx.channel().id().asLongText()); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("处理用户认证时发生错误", e); |
| | | sendAuthFailureResponse(ctx, "认证过程中发生错误: " + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 发送认证失败响应 |
| | | */ |
| | | private void sendAuthFailureResponse(ChannelHandlerContext ctx, String message) { |
| | | JSONObject response = new JSONObject(); |
| | | response.put("type", "auth_failed"); |
| | | response.put("message", message); |
| | | response.put("timestamp", LocalDateTime.now().toString()); |
| | | |
| | | ctx.channel().writeAndFlush(new TextWebSocketFrame(response.toJSONString())); |
| | | } |
| | | |
| | | /** |
| | | * 处理心跳消息 |
| | | */ |
| | | private void handlePing(ChannelHandlerContext ctx, JSONObject messageObj) { |
| | | String appUserId = userConnectionManager.getUserIdByChannelId(ctx.channel().id().asLongText()); |
| | | |
| | | JSONObject response = new JSONObject(); |
| | | response.put("type", "pong"); |
| | | log.info("心跳用户id:{}",appUserId); |
| | | response.put("appUserId", appUserId); |
| | | response.put("timestamp", LocalDateTime.now().toString()); |
| | | |
| | | ctx.channel().writeAndFlush(new TextWebSocketFrame(response.toJSONString())); |
| | | } |
| | | |
| | | /** |
| | | * 处理业务消息 |
| | | */ |
| | | private void handleBusinessMessage(ChannelHandlerContext ctx, JSONObject messageObj) { |
| | | String appUserId = userConnectionManager.getUserIdByChannelId(ctx.channel().id().asLongText()); |
| | | |
| | | if (appUserId != null) { |
| | | log.info("收到用户 {} 的业务消息: {}", appUserId, messageObj.toJSONString()); |
| | | |
| | | // 回复消息 |
| | | JSONObject response = new JSONObject(); |
| | | response.put("type", "message_response"); |
| | | response.put("appUserId", appUserId); |
| | | response.put("originalMessage", messageObj); |
| | | response.put("serverTime", LocalDateTime.now().toString()); |
| | | |
| | | ctx.channel().writeAndFlush(new TextWebSocketFrame(response.toJSONString())); |
| | | } else { |
| | | log.warn("收到未认证连接的业务消息: {}", messageObj.toJSONString()); |
| | | |
| | | // 要求先认证 |
| | | JSONObject response = new JSONObject(); |
| | | response.put("type", "auth_required"); |
| | | response.put("message", "请先进行用户认证"); |
| | | response.put("timestamp", LocalDateTime.now().toString()); |
| | | |
| | | ctx.channel().writeAndFlush(new TextWebSocketFrame(response.toJSONString())); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void handlerAdded(ChannelHandlerContext ctx) throws Exception { |
| | | // id 表示唯一的值 LongText是唯一的 |
| | | log.info("handlerAdded 被调用: {}", ctx.channel().id().asLongText()); |
| | | // shortText 可能会重复 |
| | | log.info("handlerAdded 被调用: {}", ctx.channel().id().asShortText()); |
| | | |
| | | // 注意:用户连接管理在认证时添加,这里不添加 |
| | | } |
| | | |
| | | @Override |
| | | public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { |
| | | // id 表示唯一的值 LongText是唯一的 |
| | | log.info("handlerRemoved 被调用: {}", ctx.channel().id().asLongText()); |
| | | // shortText 可能会重复 |
| | | log.info("handlerRemoved 被调用: {}", ctx.channel().id().asShortText()); |
| | | |
| | | // 从用户连接管理器中移除连接 |
| | | if (userConnectionManager != null) { |
| | | userConnectionManager.removeUserConnection(ctx.channel()); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { |
| | | log.error("WebSocket连接异常: {}", cause.getMessage(), cause); |
| | | ctx.channel().close(); |
| | | } |
| | | } |
| | |
| | | package com.xiaopeng.pushMessage.webSocket.service; |
| | | |
| | | import io.netty.channel.Channel; |
| | | import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.stereotype.Component; |
| | | import org.springframework.web.socket.TextMessage; |
| | | import org.springframework.web.socket.WebSocketSession; |
| | | |
| | | import java.util.*; |
| | | import java.util.concurrent.ConcurrentHashMap; |
| | |
| | | public class WebSocketUserConnectionManager { |
| | | |
| | | /** |
| | | * 存储用户的所有连接 |
| | | * Key: 用户ID (appUserId) |
| | | * Value: 该用户的所有 WebSocketSession 集合 |
| | | * Value: 该用户的所有连接Channel列表 |
| | | */ |
| | | private static final ConcurrentHashMap<String, Set<WebSocketSession>> userConnections = new ConcurrentHashMap<>(); |
| | | private static final ConcurrentHashMap<String, Set<Channel>> userConnections = new ConcurrentHashMap<>(); |
| | | |
| | | /** |
| | | * Key: session.getId() |
| | | * 存储连接与用户的映射关系 |
| | | * Key: ChannelId.asLongText() |
| | | * Value: 用户ID (appUserId) |
| | | */ |
| | | private static final ConcurrentHashMap<String, String> sessionToUser = new ConcurrentHashMap<>(); |
| | | private static final ConcurrentHashMap<String, String> channelToUser = new ConcurrentHashMap<>(); |
| | | |
| | | /** |
| | | * Key: session.getId() |
| | | * Value: WebSocketSession |
| | | * 存储所有连接 |
| | | * Key: ChannelId.asLongText() |
| | | * Value: Channel |
| | | */ |
| | | private static final ConcurrentHashMap<String, WebSocketSession> allConnections = new ConcurrentHashMap<>(); |
| | | |
| | | // ==================== 连接管理 ==================== |
| | | private static final ConcurrentHashMap<String, Channel> allConnections = new ConcurrentHashMap<>(); |
| | | |
| | | /** |
| | | * 添加用户连接 |
| | | * @param appUserId 用户ID |
| | | * @param channel WebSocket连接通道 |
| | | */ |
| | | public void addUserConnection(String appUserId, WebSocketSession session) { |
| | | String sessionId = session.getId(); |
| | | public void addUserConnection(String appUserId, Channel channel) { |
| | | String channelId = channel.id().asLongText(); |
| | | |
| | | userConnections.computeIfAbsent(appUserId, k -> ConcurrentHashMap.newKeySet()).add(session); |
| | | sessionToUser.put(sessionId, appUserId); |
| | | allConnections.put(sessionId, session); |
| | | // 添加到用户连接映射 |
| | | userConnections.computeIfAbsent(appUserId, k -> ConcurrentHashMap.newKeySet()) |
| | | .add(channel); |
| | | |
| | | log.info("用户连接已添加,用户ID: {}, sessionId: {}, 该用户连接数: {}", |
| | | appUserId, sessionId, userConnections.get(appUserId).size()); |
| | | // 添加到连接用户映射 |
| | | channelToUser.put(channelId, appUserId); |
| | | |
| | | // 添加到所有连接 |
| | | allConnections.put(channelId, channel); |
| | | |
| | | log.info("用户连接已添加,用户ID: {}, ChannelId: {}, 该用户连接数: {}", |
| | | appUserId, channelId, userConnections.get(appUserId).size()); |
| | | } |
| | | |
| | | /** |
| | | * 移除用户连接 |
| | | * @param channel WebSocket连接通道 |
| | | */ |
| | | public void removeUserConnection(WebSocketSession session) { |
| | | String sessionId = session.getId(); |
| | | String appUserId = sessionToUser.get(sessionId); |
| | | public void removeUserConnection(Channel channel) { |
| | | String channelId = channel.id().asLongText(); |
| | | String appUserId = channelToUser.get(channelId); |
| | | |
| | | if (appUserId != null) { |
| | | Set<WebSocketSession> userSessions = userConnections.get(appUserId); |
| | | if (userSessions != null) { |
| | | userSessions.remove(session); |
| | | if (userSessions.isEmpty()) { |
| | | // 从用户连接中移除 |
| | | Set<Channel> userChannels = userConnections.get(appUserId); |
| | | if (userChannels != null) { |
| | | userChannels.remove(channel); |
| | | // 如果用户没有连接了,删除用户映射 |
| | | if (userChannels.isEmpty()) { |
| | | userConnections.remove(appUserId); |
| | | } |
| | | } |
| | | sessionToUser.remove(sessionId); |
| | | log.info("用户连接已移除,用户ID: {}, sessionId: {}, 该用户剩余连接数: {}", |
| | | appUserId, sessionId, userSessions != null ? userSessions.size() : 0); |
| | | |
| | | // 从连接用户映射中移除 |
| | | channelToUser.remove(channelId); |
| | | |
| | | log.info("用户连接已移除,用户ID: {}, ChannelId: {}, 该用户剩余连接数: {}", |
| | | appUserId, channelId, userChannels != null ? userChannels.size() : 0); |
| | | } |
| | | |
| | | allConnections.remove(sessionId); |
| | | // 从所有连接中移除 |
| | | allConnections.remove(channelId); |
| | | } |
| | | |
| | | // ==================== 消息发送 ==================== |
| | | |
| | | /** |
| | | * 向指定用户的所有设备发送消息 |
| | | * @param appUserId 用户ID |
| | | * @param message 消息内容 |
| | | * @return 成功发送的设备数 |
| | | */ |
| | | public int sendMessageToUser(String appUserId, String message) { |
| | | Set<WebSocketSession> sessions = userConnections.get(appUserId); |
| | | if (sessions == null || sessions.isEmpty()) { |
| | | Set<Channel> userChannels = userConnections.get(appUserId); |
| | | if (userChannels == null || userChannels.isEmpty()) { |
| | | log.warn("用户 {} 没有活跃连接", appUserId); |
| | | return 0; |
| | | } |
| | | |
| | | int successCount = 0; |
| | | for (WebSocketSession session : sessions) { |
| | | if (session != null && session.isOpen()) { |
| | | for (Channel channel : userChannels) { |
| | | if (channel != null && channel.isActive()) { |
| | | try { |
| | | synchronized (session) { |
| | | session.sendMessage(new TextMessage(message)); |
| | | } |
| | | channel.writeAndFlush(new TextWebSocketFrame(message)); |
| | | successCount++; |
| | | } catch (Exception e) { |
| | | log.error("向用户 {} 发送消息失败,sessionId: {}, 错误: {}", |
| | | appUserId, session.getId(), e.getMessage()); |
| | | log.error("向用户 {} 发送消息失败,连接ID: {}, 错误: {}", |
| | | appUserId, channel.id().asLongText(), e.getMessage()); |
| | | } |
| | | } |
| | | } |
| | | |
| | | log.info("向用户 {} 发送消息完成,成功发送到 {} 个设备", appUserId, successCount); |
| | | log.info("向用户 {} 发送消息完成,成功发送到 {} 个设备,消息内容: {}", |
| | | appUserId, successCount, message); |
| | | return successCount; |
| | | } |
| | | |
| | | /** |
| | | * 向指定 session 发送消息 |
| | | * 向指定连接发送消息 |
| | | * @param channelId 连接ID |
| | | * @param message 消息内容 |
| | | * @return 是否发送成功 |
| | | */ |
| | | public boolean sendMessageToChannel(String sessionId, String message) { |
| | | WebSocketSession session = allConnections.get(sessionId); |
| | | if (session != null && session.isOpen()) { |
| | | public boolean sendMessageToChannel(String channelId, String message) { |
| | | Channel channel = allConnections.get(channelId); |
| | | if (channel != null && channel.isActive()) { |
| | | try { |
| | | synchronized (session) { |
| | | session.sendMessage(new TextMessage(message)); |
| | | } |
| | | log.info("消息已发送到连接: {}", sessionId); |
| | | channel.writeAndFlush(new TextWebSocketFrame(message)); |
| | | log.info("消息已发送到连接: {}, 内容: {}", channelId, message); |
| | | return true; |
| | | } catch (Exception e) { |
| | | log.error("发送消息失败,sessionId: {}, 错误: {}", sessionId, e.getMessage()); |
| | | log.error("发送消息失败,连接ID: {}, 错误: {}", channelId, e.getMessage()); |
| | | return false; |
| | | } |
| | | } else { |
| | | log.warn("连接不存在或已关闭,sessionId: {}", sessionId); |
| | | log.warn("连接不存在或已关闭,ChannelId: {}", channelId); |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 向所有连接广播消息 |
| | | * @param message 消息内容 |
| | | * @return 成功发送的连接数 |
| | | */ |
| | | public int broadcastMessage(String message) { |
| | | int successCount = 0; |
| | | for (WebSocketSession session : allConnections.values()) { |
| | | if (session != null && session.isOpen()) { |
| | | for (Channel channel : allConnections.values()) { |
| | | if (channel != null && channel.isActive()) { |
| | | try { |
| | | synchronized (session) { |
| | | session.sendMessage(new TextMessage(message)); |
| | | } |
| | | channel.writeAndFlush(new TextWebSocketFrame(message)); |
| | | successCount++; |
| | | } catch (Exception e) { |
| | | log.error("广播消息失败,sessionId: {}, 错误: {}", session.getId(), e.getMessage()); |
| | | log.error("广播消息失败,连接ID: {}, 错误: {}", |
| | | channel.id().asLongText(), e.getMessage()); |
| | | } |
| | | } |
| | | } |
| | | log.info("广播消息完成,成功发送到 {} 个连接", successCount); |
| | | log.info("广播消息完成,成功发送到 {} 个连接,消息内容: {}", successCount, message); |
| | | return successCount; |
| | | } |
| | | |
| | | /** |
| | | * 向多个用户发送消息 |
| | | * @param appUserIds 用户ID列表 |
| | | * @param message 消息内容 |
| | | * @return 成功发送的用户数 |
| | | */ |
| | | public int sendMessageToUsers(List<String> appUserIds, String message) { |
| | | int successUserCount = 0; |
| | |
| | | return successUserCount; |
| | | } |
| | | |
| | | // ==================== 查询 ==================== |
| | | |
| | | public Set<WebSocketSession> getUserConnections(String appUserId) { |
| | | /** |
| | | * 获取用户的所有连接 |
| | | * @param appUserId 用户ID |
| | | * @return 连接列表 |
| | | */ |
| | | public Set<Channel> getUserConnections(String appUserId) { |
| | | return userConnections.getOrDefault(appUserId, Collections.emptySet()); |
| | | } |
| | | |
| | | /** |
| | | * 获取用户连接数 |
| | | * @param appUserId 用户ID |
| | | * @return 连接数 |
| | | */ |
| | | public int getUserConnectionCount(String appUserId) { |
| | | Set<WebSocketSession> sessions = userConnections.get(appUserId); |
| | | return sessions != null ? sessions.size() : 0; |
| | | Set<Channel> userChannels = userConnections.get(appUserId); |
| | | return userChannels != null ? userChannels.size() : 0; |
| | | } |
| | | |
| | | /** |
| | | * 获取总连接数 |
| | | * @return 总连接数 |
| | | */ |
| | | public int getTotalConnectionCount() { |
| | | return allConnections.size(); |
| | | } |
| | | |
| | | /** |
| | | * 获取在线用户数 |
| | | * @return 在线用户数 |
| | | */ |
| | | public int getOnlineUserCount() { |
| | | return userConnections.size(); |
| | | } |
| | | |
| | | /** |
| | | * 获取所有在线用户ID |
| | | * @return 用户ID集合 |
| | | */ |
| | | public Set<String> getOnlineUserIds() { |
| | | return userConnections.keySet(); |
| | | } |
| | | |
| | | /** |
| | | * 检查用户是否在线 |
| | | * @param appUserId 用户ID |
| | | * @return 是否在线 |
| | | */ |
| | | public boolean isUserOnline(String appUserId) { |
| | | Set<WebSocketSession> sessions = userConnections.get(appUserId); |
| | | if (sessions == null || sessions.isEmpty()) { |
| | | Set<Channel> userChannels = userConnections.get(appUserId); |
| | | if (userChannels == null || userChannels.isEmpty()) { |
| | | return false; |
| | | } |
| | | return sessions.stream().anyMatch(WebSocketSession::isOpen); |
| | | } |
| | | |
| | | public boolean isConnectionExists(String sessionId) { |
| | | WebSocketSession session = allConnections.get(sessionId); |
| | | return session != null && session.isOpen(); |
| | | // 检查是否有活跃连接 |
| | | return userChannels.stream().anyMatch(Channel::isActive); |
| | | } |
| | | |
| | | /** |
| | | * 根据 sessionId 获取用户ID |
| | | * 检查连接是否存在 |
| | | * @param channelId 连接ID |
| | | * @return 是否存在 |
| | | */ |
| | | public String getUserIdBySessionId(String sessionId) { |
| | | return sessionToUser.get(sessionId); |
| | | public boolean isConnectionExists(String channelId) { |
| | | Channel channel = allConnections.get(channelId); |
| | | return channel != null && channel.isActive(); |
| | | } |
| | | |
| | | /** |
| | | * 根据连接ID获取用户ID |
| | | * @param channelId 连接ID |
| | | * @return 用户ID |
| | | */ |
| | | public String getUserIdByChannelId(String channelId) { |
| | | return channelToUser.get(channelId); |
| | | } |
| | | |
| | | /** |
| | | * 获取连接统计信息 |
| | | * @return 统计信息 |
| | | */ |
| | | public Map<String, Object> getConnectionStats() { |
| | | Map<String, Object> stats = new HashMap<>(); |
| | | stats.put("totalConnections", getTotalConnectionCount()); |
| | | stats.put("onlineUsers", getOnlineUserCount()); |
| | | stats.put("onlineUserIds", getOnlineUserIds()); |
| | | |
| | | // 统计每个用户的连接数 |
| | | Map<String, Integer> userConnectionCounts = new HashMap<>(); |
| | | for (Map.Entry<String, Set<WebSocketSession>> entry : userConnections.entrySet()) { |
| | | for (Map.Entry<String, Set<Channel>> entry : userConnections.entrySet()) { |
| | | userConnectionCounts.put(entry.getKey(), entry.getValue().size()); |
| | | } |
| | | stats.put("userConnectionCounts", userConnectionCounts); |
| | |
| | | Boolean vehicleTrunkOpenStatus = xpVehicleService.getVehicleTrunkOpenStatus(String.valueOf(uid), id); |
| | | return R.ok(vehicleTrunkOpenStatus); |
| | | } |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | public R<String> generateBluetoothUnlockToken(){ |
| | | |
| | | } |
| | | } |