Kaynağa Gözat

推荐用户时,新用户如果位置和推荐者用户在同一个区,则新用户默认归属区域和服务中心为推荐者的区域和服务中心

lsd 3 ay önce
ebeveyn
işleme
563540dc95

+ 1 - 1
leromro-admin/src/main/java/com/leromro/web/service/impl/AggregationServiceImpl.java

@@ -33,7 +33,7 @@ public class AggregationServiceImpl implements IAggregationService {
         LoginUser loginUser = tokenService.getLoginUser(token);
 
         // 推荐处理
-        iInviteUserService.referrerUserHandler(loginUser.getUserId(), isNewUser, loginBody.getReferrerType(),loginBody.getReferrerId());
+        iInviteUserService.referrerUserHandler(loginUser.getUserId(), isNewUser, loginBody.getReferrerType(),loginBody.getReferrerId(),loginBody);
         return token;
     }
 }

+ 2 - 0
leromro-admin/src/main/resources/application-druid.yml

@@ -34,6 +34,8 @@ spring:
             minEvictableIdleTimeMillis: 300000
             # 配置一个连接在池中最大生存的时间,单位是毫秒
             maxEvictableIdleTimeMillis: 900000
+            # 保持连接活跃
+            keepAlive: true
             # 配置检测连接是否有效
             validationQuery: SELECT 1 FROM DUAL
             testWhileIdle: true

+ 8 - 1
leromro-admin/src/main/resources/application.yml

@@ -73,6 +73,8 @@ user:
 
 # Spring配置
 spring:
+  transaction:
+    default-timeout: 60  # 单位:秒
   # 资源信息
   messages:
     # 国际化资源文件路径
@@ -183,4 +185,9 @@ aliyun:
     sign-type: RSA2
     cert-path: classpath:cert/appCertPublicKey.crt
     alipay-public-cert-path: classpath:cert/alipayCertPublicKey_RSA2.crt
-    root-cert-path:  classpath:cert/alipayRootCert.crt
+    root-cert-path:  classpath:cert/alipayRootCert.crt
+
+tencent:
+  map:
+    key: KFEBZ-P2GKZ-A5PX4-7Q6Y7-KXOBF-XCB4C
+    base-url: https://apis.map.qq.com

+ 28 - 0
leromro-common/src/main/java/com/leromro/common/config/TencentMapProperties.java

@@ -0,0 +1,28 @@
+package com.leromro.common.config;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+@Component
+@ConfigurationProperties(prefix = "tencent.map")
+public class TencentMapProperties {
+
+    private String key;
+    private String baseUrl;
+
+    public String getKey() {
+        return key;
+    }
+
+    public void setKey(String key) {
+        this.key = key;
+    }
+
+    public String getBaseUrl() {
+        return baseUrl;
+    }
+
+    public void setBaseUrl(String baseUrl) {
+        this.baseUrl = baseUrl;
+    }
+}

+ 12 - 0
leromro-common/src/main/java/com/leromro/common/core/domain/model/LoginBody.java

@@ -42,6 +42,18 @@ public class LoginBody
      */
     private Long referrerId;
 
+    /**
+     * 用户当前经度
+     * @return
+     */
+    private Double longitude;
+
+    /**
+     * 用户当前纬度
+     * @return
+     */
+    private Double latitude;
+
     public String getUsername()
     {
         return username;

+ 222 - 0
leromro-common/src/main/java/com/leromro/common/utils/TencentMapUtils.java

@@ -0,0 +1,222 @@
+package com.leromro.common.utils;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.PropertyNamingStrategies;
+import com.leromro.common.config.TencentMapProperties;
+import lombok.Data;
+import org.springframework.stereotype.Component;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Component
+public class TencentMapUtils {
+
+    private final TencentMapProperties properties;
+    private final RestTemplate restTemplate;
+    private static final ObjectMapper objectMapper = new ObjectMapper();
+    static {
+        // 忽略未知属性
+        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+        // 添加以下配置即可支持下划线转驼峰
+        objectMapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
+    }
+
+    public TencentMapUtils(TencentMapProperties properties, RestTemplate restTemplate) {
+        this.properties = properties;
+        this.restTemplate = restTemplate;
+    }
+
+    // ====== 地理编码(地址 -> 经纬度)======
+    public GeocodeResult geocode(String address) throws Exception {
+        String url = properties.getBaseUrl() + "/ws/geocoder/v1/?address={address}&key={key}";
+        Map<String, Object> params = new HashMap<>();
+        params.put("address", address);
+        params.put("key", properties.getKey());
+
+        String response = restTemplate.getForObject(url, String.class, params);
+        return parseResponse(response, GeocodeResult.class);
+    }
+
+    // ====== 逆地理编码(经纬度 -> 地址)======
+    public ReverseGeocodeResult reverseGeocode(double latitude, double longitude) {
+        String location = latitude + "," + longitude;
+        String url = properties.getBaseUrl() + "/ws/geocoder/v1/?location={location}&key={key}";
+
+        Map<String, Object> params = new HashMap<>();
+        params.put("location", location);
+        params.put("key", properties.getKey());
+
+        String response = restTemplate.getForObject(url, String.class, params);
+        return parseResponse(response, ReverseGeocodeResult.class);
+    }
+
+    // ====== 距离计算 ======
+    public DistanceResult calculateDistance(List<String> origins, List<String> destinations) throws Exception {
+        String originsStr = String.join(";", origins);
+        String destsStr = String.join(";", destinations);
+
+        String url = properties.getBaseUrl() + "/ws/distance/v1/?origins={origins}&destinations={destinations}&key={key}";
+
+        Map<String, Object> params = new HashMap<>();
+        params.put("origins", originsStr);
+        params.put("destinations", destsStr);
+        params.put("key", properties.getKey());
+
+        String response = restTemplate.getForObject(url, String.class, params);
+        return parseResponse(response, DistanceResult.class);
+    }
+
+    // ====== 周边搜索 ======
+    public PlaceSearchResult searchNearby(String keyword, double latitude, double longitude, int radius) throws Exception {
+        String boundary = "nearby(" + latitude + "," + longitude + "," + radius + ")";
+        String url = properties.getBaseUrl() + "/ws/place/v1/search?keyword={keyword}&boundary={boundary}&key={key}";
+
+        Map<String, Object> params = new HashMap<>();
+        params.put("keyword", keyword);
+        params.put("boundary", boundary);
+        params.put("key", properties.getKey());
+
+        String response = restTemplate.getForObject(url, String.class, params);
+        return parseResponse(response, PlaceSearchResult.class);
+    }
+
+    // ====== 路径规划(驾车)=====
+    public DirectionResult getDrivingRoute(double fromLat, double fromLng, double toLat, double toLng) throws Exception {
+        String from = fromLat + "," + fromLng;
+        String to = toLat + "," + toLng;
+        String url = properties.getBaseUrl() + "/ws/direction/v1/driving/?from={from}&to={to}&key={key}";
+
+        Map<String, Object> params = new HashMap<>();
+        params.put("from", from);
+        params.put("to", to);
+        params.put("key", properties.getKey());
+
+        String response = restTemplate.getForObject(url, String.class, params);
+        return parseResponse(response, DirectionResult.class);
+    }
+
+    // ====== 工具方法 ======
+    private <T> T parseResponse(String response, Class<T> clazz) {
+        try {
+            return objectMapper.readValue(response, clazz);
+        } catch (JsonProcessingException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    // ====== 数据结构定义 ======
+
+    @Data
+    public static class GeocodeResult {
+        private int status;
+        private String message;
+        private Result result;
+
+        @Data
+        public static class Result {
+            private Location location;
+            private String address;
+        }
+
+        @Data
+        public static class Location {
+            private double lat;
+            private double lng;
+        }
+    }
+
+    @Data
+    public static class ReverseGeocodeResult {
+        private int status;
+        private String message;
+        private Result result;
+
+        @Data
+        public static class Result {
+            private String address;
+            private AddressComponent addressComponent; // 地址组件
+            private AdInfo adInfo; // 行政区划信息
+            private Location location;
+        }
+
+        @Data
+        public static class AddressComponent {
+            private String nation;
+            private String province;
+            private String city;
+            private String district;
+            private String street;
+        }
+
+        @Data
+        public static class AdInfo {
+            private String adcode;     // 行政区划编码
+            private String province;   // 省
+            private String city;       // 市
+            private String district;   // 区
+        }
+
+        @Data
+        public static class Location {
+            private double lat;
+            private double lng;
+        }
+    }
+
+    @Data
+    public static class DistanceResult {
+        private int status;
+        private String message;
+        private List<DistanceElement> result;
+
+        @Data
+        public static class DistanceElement {
+            private int distance; // 单位:米
+            private String duration; // 时间描述
+        }
+    }
+
+    @Data
+    public static class PlaceSearchResult {
+        private int status;
+        private String message;
+        private List<Poi> data;
+
+        @Data
+        public static class Poi {
+            private String title;
+            private Location location;
+            private String address;
+        }
+
+        @Data
+        public static class Location {
+            private double lat;
+            private double lng;
+        }
+    }
+
+    @Data
+    public static class DirectionResult {
+        private int status;
+        private String message;
+        private RouteResult result;
+
+        @Data
+        public static class RouteResult {
+            private Route route;
+
+            @Data
+            public static class Route {
+                private double distance; // 总距离(单位:米)
+                private double duration; // 总时间(单位:秒)
+                private String instruction; // 导航说明
+            }
+        }
+    }
+}

+ 4 - 1
leromro-core/src/main/java/com/leromro/core/service/IInviteUserService.java

@@ -1,6 +1,7 @@
 package com.leromro.core.service;
 
 import com.baomidou.mybatisplus.extension.service.IService;
+import com.leromro.common.core.domain.model.LoginBody;
 import com.leromro.core.domain.InviteUser;
 
 import java.util.List;
@@ -63,12 +64,14 @@ public interface IInviteUserService extends IService<InviteUser>
 
     /**
      * 邀请用户处理
+     *
      * @param userId
      * @param isNewUser
      * @param referrerType
      * @param referrerId
+     * @param loginBody
      */
-    public void referrerUserHandler(Long userId, boolean isNewUser, String referrerType, Long referrerId);
+    public void referrerUserHandler(Long userId, boolean isNewUser, String referrerType, Long referrerId, LoginBody loginBody);
 
     /**
      * 生成小程序用户邀请二维码

+ 64 - 11
leromro-core/src/main/java/com/leromro/core/service/impl/InviteUserServiceImpl.java

@@ -1,10 +1,14 @@
 package com.leromro.core.service.impl;
 
+import cn.hutool.core.util.StrUtil;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.leromro.common.constant.SysConfigConstants;
+import com.leromro.common.core.domain.entity.SysDept;
 import com.leromro.common.core.domain.entity.SysUser;
+import com.leromro.common.core.domain.model.LoginBody;
 import com.leromro.common.enums.ReferrerTypeEnum;
 import com.leromro.common.enums.UserPointChangeTypeEnum;
+import com.leromro.common.utils.TencentMapUtils;
 import com.leromro.common.utils.WeChatMiniProgramUtil;
 import com.leromro.core.domain.InviteUser;
 import com.leromro.core.domain.UserPointChange;
@@ -14,10 +18,13 @@ import com.leromro.core.service.ISecondOrderService;
 import com.leromro.core.service.IUserPointChangeService;
 import com.leromro.system.service.ISysConfigService;
 import com.leromro.system.service.ISysUserService;
+import com.leromro.system.service.impl.SysDeptServiceImpl;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
+import java.math.BigDecimal;
+import java.math.RoundingMode;
 import java.util.Date;
 import java.util.List;
 
@@ -43,6 +50,10 @@ public class InviteUserServiceImpl extends ServiceImpl<InviteUserMapper, InviteU
     private WeChatMiniProgramUtil weChatMiniProgramUtil;
     @Autowired
     private ISecondOrderService iSecondOrderService;
+    @Autowired
+    private SysDeptServiceImpl sysDeptServiceImpl;
+    @Autowired
+    private TencentMapUtils tencentMapUtils;
 
     /**
      * 查询邀请用户记录
@@ -118,12 +129,14 @@ public class InviteUserServiceImpl extends ServiceImpl<InviteUserMapper, InviteU
 
     /**
      * 推荐用户处理
-     * @param userId 用户id
+     *
+     * @param userId       用户id
      * @param referrerType 推荐类型 1:用户 2:区域公司 3:服务中心
-     * @param referrerId 推荐者id
+     * @param referrerId   推荐者id
+     * @param loginBody 登录信息
      */
     @Transactional
-    public void referrerUserHandler(Long userId, boolean isNewUser, String referrerType, Long referrerId) {
+    public void referrerUserHandler(Long userId, boolean isNewUser, String referrerType, Long referrerId, LoginBody loginBody) {
         // 如果没有推荐者,则不处理
         if (referrerId == null) {
             return;
@@ -150,24 +163,64 @@ public class InviteUserServiceImpl extends ServiceImpl<InviteUserMapper, InviteU
         }
 
         // 保存用户推荐信息
-        SysUser referrerUser = new SysUser();
+        SysUser paramForUpdateUser = new SysUser();
         if(ReferrerTypeEnum.USER.getCode().equals(referrerType)){
-            referrerUser.setReferrerUserId(referrerId);
+            paramForUpdateUser.setReferrerUserId(referrerId);
+
+            // 如果登录有位置信息,则获取位置归属区域代码
+            String userAreaCode = null;
+            if(loginBody.getLongitude() != null && loginBody.getLatitude() != null){
+                // 经纬度最大保留6位小数
+                double longitude = new BigDecimal(loginBody.getLongitude()).setScale(6, RoundingMode.DOWN).doubleValue();
+                double latitude = new BigDecimal(loginBody.getLatitude()).setScale(6, RoundingMode.DOWN).doubleValue();
+                // 获取位置归属区域代码
+                TencentMapUtils.ReverseGeocodeResult reverseGeocode = tencentMapUtils.reverseGeocode(latitude, longitude);
+                if(reverseGeocode != null && reverseGeocode.getResult() != null && reverseGeocode.getResult().getAdInfo() != null){
+                    userAreaCode = reverseGeocode.getResult().getAdInfo().getAdcode();
+                }
+            }
+            /**
+             *  如果用户当前位置和推荐者归属区域是同一个,则默认归属于推荐用户所属区域公司和服务中心
+             */
+            // 获取推荐者用户信息
+            SysUser referrerUserInfo = iSysUserService.getById(referrerId);
+            // 获取区域用户归属区域信息
+            SysDept referrerUserAreaInfo = null;
+            if(referrerUserInfo.getAreaId() != null){
+                referrerUserAreaInfo = sysDeptServiceImpl.selectDeptById(referrerUserInfo.getAreaId());
+            }
+            // 如果推荐者用户归属区域和用户当前位置归属区域是同一个,则默认归属于推荐用户所属区域公司和服务中心
+            if(referrerUserAreaInfo != null && StrUtil.isNotBlank(userAreaCode) && userAreaCode.equals(referrerUserAreaInfo.getDistrictCode())){
+                paramForUpdateUser.setAreaId(referrerUserInfo.getAreaId());
+            }
+            if(referrerUserAreaInfo != null && StrUtil.isNotBlank(userAreaCode)
+                    && userAreaCode.equals(referrerUserAreaInfo.getDistrictCode()) && referrerUserInfo.getServiceCentreId() != null){
+                paramForUpdateUser.setServiceCentreId(referrerUserInfo.getServiceCentreId());
+            }
         }else if (ReferrerTypeEnum.AREA_COMPANY.getCode().equals(referrerType)){
-            referrerUser.setReferrerAreaId(referrerId);
+            paramForUpdateUser.setReferrerAreaId(referrerId);
             // 默认归属区域公司为推荐区域公司id
             if(userInfo.getAreaId() == null){
-                referrerUser.setAreaId(referrerId);
+                paramForUpdateUser.setAreaId(referrerId);
             }
         }else if (ReferrerTypeEnum.SERVICE_CENTRE.getCode().equals(referrerType)){
-            referrerUser.setReferrerServiceCentreId(referrerId);
+            paramForUpdateUser.setReferrerServiceCentreId(referrerId);
             // 默认归属服务中心公司为推荐服务中心id
             if(userInfo.getServiceCentreId() == null){
-                referrerUser.setServiceCentreId(referrerId);
+                paramForUpdateUser.setServiceCentreId(referrerId);
+            }
+
+            /**
+             * 默认归属于服务中心归属区域公司
+             */
+            // 获取服务中心归属区域公司
+            SysDept areaInfo = sysDeptServiceImpl.getAreaByServiceCenterId(referrerId);
+            if(userInfo.getAreaId() == null && areaInfo != null && areaInfo.getDeptId() != null){
+                paramForUpdateUser.setAreaId(areaInfo.getDeptId());
             }
         }
-        referrerUser.setUserId(userId);
-        iSysUserService.updateUserProfile(referrerUser);
+        paramForUpdateUser.setUserId(userId);
+        iSysUserService.updateUserProfile(paramForUpdateUser);
 
         // 新增邀请记录
         InviteUser inviteUser = new InviteUser();

+ 3 - 0
leromro-framework/src/main/java/com/leromro/framework/web/service/SysLoginService.java

@@ -33,6 +33,7 @@ import org.springframework.security.authentication.BadCredentialsException;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 import org.springframework.security.core.Authentication;
 import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
 
 import javax.annotation.Resource;
 import java.util.HashMap;
@@ -71,6 +72,7 @@ public class SysLoginService
      * @param code 临时登录凭证
      * @return 结果
      */
+    @Transactional
     public Map<String, Object> login(String code)
     {
 
@@ -145,6 +147,7 @@ public class SysLoginService
         return result;
     }
 
+    @Transactional
     public String login(String username, String password, String code, String uuid)
     {
         // 验证码校验

+ 2 - 0
leromro-system/src/main/java/com/leromro/system/mapper/SysDeptMapper.java

@@ -126,4 +126,6 @@ public interface SysDeptMapper extends BaseMapper<SysDept>
     List<SysDept> getAlldept();
 
     String selectPointList(Long deptId);
+
+    SysDept getAreaByServiceCenterId(Long serviceCenterId);
 }

+ 5 - 0
leromro-system/src/main/java/com/leromro/system/service/ISysDeptService.java

@@ -146,4 +146,9 @@ public interface ISysDeptService extends IService<SysDept>
     void updateDeptPointList(SysDept sysDept);
 
     String getPointList(Long deptId);
+
+    /**
+     * 查询服务中心归属的区域信息
+     */
+    SysDept getAreaByServiceCenterId(Long serviceCenterId);
 }

+ 5 - 0
leromro-system/src/main/java/com/leromro/system/service/impl/SysDeptServiceImpl.java

@@ -354,6 +354,11 @@ public class SysDeptServiceImpl extends ServiceImpl<SysDeptMapper, SysDept> impl
         return PointList;
     }
 
+    @Override
+    public SysDept getAreaByServiceCenterId(Long serviceCenterId) {
+        return sysDeptMapper.getAreaByServiceCenterId(serviceCenterId);
+    }
+
     /**
      * 递归列表
      */

+ 1 - 0
leromro-system/src/main/java/com/leromro/system/service/impl/SysUserServiceImpl.java

@@ -346,6 +346,7 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
      * @return 结果
      */
     @Override
+    @Transactional
     public int updateUserProfile(SysUser user)
     {
         return userMapper.updateById(user);

+ 10 - 1
leromro-system/src/main/resources/mapper/system/SysDeptMapper.xml

@@ -114,7 +114,16 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 		select point_list from sys_dept where dept_id = #{deptId}
 	</select>
 
-	<insert id="insertDept" parameterType="SysDept">
+    <select id="getAreaByServiceCenterId" resultType="com.leromro.common.core.domain.entity.SysDept">
+		select *
+		from sys_dept a
+		<where>
+			find_in_set(a.dept_id, (select sys_dept.ancestors from sys_dept where sys_dept.dept_id = #{serviceCenterId}))
+			and a.area_type = 3
+		</where>
+	</select>
+
+    <insert id="insertDept" parameterType="SysDept">
         insert into sys_dept(
         <if test="deptId != null and deptId != 0">dept_id,</if>
         <if test="parentId != null and parentId != 0">parent_id,</if>