粉丝数构造
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
package com.rj.controller;
|
||||
|
||||
import com.rj.dto.LbFanRecursiveFetchRequest;
|
||||
import com.rj.dto.LbFanSimulateLoginRequest;
|
||||
import com.rj.entity.LbFanManagement;
|
||||
import com.rj.service.ILbFanManagementService;
|
||||
@@ -52,7 +53,7 @@ public class LbFanManagementController {
|
||||
|
||||
@PostMapping("/simulate-login")
|
||||
@Operation(
|
||||
summary = "批量模拟第三方登录",
|
||||
summary = "批量模拟第三方登录,并发现粉丝",
|
||||
description =
|
||||
"按 tenantId 从 lb_third_integration_config 读取 login_api_path 等配置,"
|
||||
+ "对每个手机号调用第三方登录接口并解析返回 JSON;"
|
||||
@@ -64,6 +65,20 @@ public class LbFanManagementController {
|
||||
return toResponse(result);
|
||||
}
|
||||
|
||||
@PostMapping("/recursive-fetch")
|
||||
@Operation(
|
||||
summary = "递归发现所有粉丝(包括粉丝的粉丝)",
|
||||
description =
|
||||
"按 tenantId 从 lb_third_integration_config 读取配置,"
|
||||
+ "对每个手机号登录后拉取粉丝,再递归登录粉丝拉取粉丝的粉丝;"
|
||||
+ "支持最大递归深度限制和跳过已存在粉丝")
|
||||
public ResponseEntity<Map<String, Object>> recursiveFetchFans(
|
||||
@Parameter(description = "递归发现粉丝请求", required = true)
|
||||
@RequestBody LbFanRecursiveFetchRequest request) {
|
||||
Map<String, Object> result = lbFanManagementService.recursiveFetchFans(request);
|
||||
return toResponse(result);
|
||||
}
|
||||
|
||||
@GetMapping("/list")
|
||||
@Operation(summary = "分页查询粉丝")
|
||||
public ResponseEntity<Map<String, Object>> list(
|
||||
|
||||
29
src/main/java/com/rj/dto/LbFanRecursiveFetchRequest.java
Normal file
29
src/main/java/com/rj/dto/LbFanRecursiveFetchRequest.java
Normal file
@@ -0,0 +1,29 @@
|
||||
package com.rj.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 粉丝管理:递归发现所有粉丝(包括粉丝的粉丝)。
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "递归发现粉丝请求")
|
||||
public class LbFanRecursiveFetchRequest {
|
||||
|
||||
@Schema(description = "租户 id,关联 lb_third_integration_config.tenant_id", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String tenantId;
|
||||
|
||||
@Schema(description = "手机号列表(作为根节点开始递归发现)", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private List<String> mobiles;
|
||||
|
||||
@Schema(description = "登录密码;未传时默认为 123456")
|
||||
private String password;
|
||||
|
||||
@Schema(description = "最大递归深度;不传或传 0 时不限深度")
|
||||
private Integer maxDepth;
|
||||
|
||||
@Schema(description = "是否跳过已存在的粉丝;不传时默认为 true")
|
||||
private Boolean skipExisting;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.rj.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.rj.dto.LbFanRecursiveFetchRequest;
|
||||
import com.rj.dto.LbFanSimulateLoginRequest;
|
||||
import com.rj.entity.LbFanManagement;
|
||||
|
||||
@@ -38,4 +39,11 @@ public interface ILbFanManagementService extends IService<LbFanManagement> {
|
||||
* @return 树形结构数据
|
||||
*/
|
||||
Map<String, Object> getFanTree(String tenantId, String parentMobile);
|
||||
|
||||
/**
|
||||
* 递归发现所有粉丝(包括粉丝的粉丝)
|
||||
* @param request 请求参数
|
||||
* @return 递归发现结果
|
||||
*/
|
||||
Map<String, Object> recursiveFetchFans(LbFanRecursiveFetchRequest request);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.rj.service.impl;
|
||||
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.rj.dto.LbFanRecursiveFetchRequest;
|
||||
import com.rj.dto.LbFanSimulateLoginRequest;
|
||||
import com.rj.dto.hxr.HxrFansApiContext;
|
||||
import com.rj.dto.hxr.HxrLbFansPageData;
|
||||
@@ -850,4 +851,217 @@ public class LbFanManagementServiceImpl
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> recursiveFetchFans(LbFanRecursiveFetchRequest request) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
try {
|
||||
if (request == null) {
|
||||
result.put("success", false);
|
||||
result.put("message", "请求体不能为空");
|
||||
return result;
|
||||
}
|
||||
if (request.getTenantId() == null || request.getTenantId().trim().isEmpty()) {
|
||||
result.put("success", false);
|
||||
result.put("message", "tenantId不能为空");
|
||||
return result;
|
||||
}
|
||||
if (request.getMobiles() == null || request.getMobiles().isEmpty()) {
|
||||
result.put("success", false);
|
||||
result.put("message", "手机号列表不能为空");
|
||||
return result;
|
||||
}
|
||||
|
||||
String tenantId = request.getTenantId().trim();
|
||||
String password = request.getPassword() != null && !request.getPassword().trim().isEmpty()
|
||||
? request.getPassword().trim()
|
||||
: DEFAULT_SIMULATE_LOGIN_PASSWORD;
|
||||
int maxDepth = request.getMaxDepth() != null && request.getMaxDepth() > 0
|
||||
? request.getMaxDepth()
|
||||
: Integer.MAX_VALUE;
|
||||
boolean skipExisting = request.getSkipExisting() == null || request.getSkipExisting();
|
||||
|
||||
Optional<HxrUserLoginApiContext> loginCtxOpt =
|
||||
lbThirdIntegrationConfigService.resolveUserLoginApiContext(tenantId);
|
||||
if (loginCtxOpt.isEmpty()) {
|
||||
result.put("success", false);
|
||||
result.put("message",
|
||||
"未找到该租户的第三方集成配置,或配置未启用、URL 不完整(请检查 lb_third_integration_config)");
|
||||
return result;
|
||||
}
|
||||
HxrUserLoginApiContext loginCtx = loginCtxOpt.get();
|
||||
|
||||
Map<String, Object> stats = new LinkedHashMap<>();
|
||||
stats.put("totalLoginAttempts", 0);
|
||||
stats.put("totalLoginSuccess", 0);
|
||||
stats.put("totalFansSaved", 0);
|
||||
stats.put("totalFansFetched", 0);
|
||||
stats.put("maxDepthReached", 0);
|
||||
|
||||
Map<String, Map<String, Object>> processedMobiles = new LinkedHashMap<>();
|
||||
List<String> rootMobiles = new ArrayList<>();
|
||||
for (String mobile : request.getMobiles()) {
|
||||
if (mobile != null && !mobile.trim().isEmpty()) {
|
||||
rootMobiles.add(mobile.trim());
|
||||
}
|
||||
}
|
||||
|
||||
recursiveFetchLevel(rootMobiles, null, tenantId, password, loginCtx,
|
||||
0, maxDepth, skipExisting, stats, processedMobiles);
|
||||
|
||||
result.put("success", true);
|
||||
result.put("message", "递归发现粉丝完成");
|
||||
result.put("stats", stats);
|
||||
result.put("processedMobiles", processedMobiles);
|
||||
return result;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("递归发现粉丝异常", e);
|
||||
result.put("success", false);
|
||||
result.put("message", "递归发现粉丝异常:" + e.getMessage());
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private void recursiveFetchLevel(List<String> mobiles,
|
||||
Map<String, Object> parentInfo,
|
||||
String tenantId,
|
||||
String password,
|
||||
HxrUserLoginApiContext loginCtx,
|
||||
int currentDepth,
|
||||
int maxDepth,
|
||||
boolean skipExisting,
|
||||
Map<String, Object> stats,
|
||||
Map<String, Map<String, Object>> processedMobiles) {
|
||||
if (currentDepth >= maxDepth) {
|
||||
return;
|
||||
}
|
||||
if (mobiles == null || mobiles.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int depth = currentDepth + 1;
|
||||
if (depth > (Integer) stats.get("maxDepthReached")) {
|
||||
stats.put("maxDepthReached", depth);
|
||||
}
|
||||
|
||||
List<String> nextLevelMobiles = new ArrayList<>();
|
||||
|
||||
for (String mobile : mobiles) {
|
||||
if (mobile == null || mobile.trim().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
String trimmedMobile = mobile.trim();
|
||||
|
||||
if (processedMobiles.containsKey(trimmedMobile)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Map<String, Object> mobileResult = new LinkedHashMap<>();
|
||||
mobileResult.put("mobile", trimmedMobile);
|
||||
mobileResult.put("depth", depth);
|
||||
mobileResult.put("loginSuccess", false);
|
||||
mobileResult.put("saved", false);
|
||||
mobileResult.put("fansFetched", 0);
|
||||
mobileResult.put("fansSaved", 0);
|
||||
|
||||
if (skipExisting) {
|
||||
LbFanManagement existing = this.getOne(
|
||||
new LambdaQueryWrapper<LbFanManagement>()
|
||||
.eq(LbFanManagement::getTenantId, tenantId)
|
||||
.eq(LbFanManagement::getMobile, trimmedMobile)
|
||||
.last("LIMIT 1"));
|
||||
if (existing != null) {
|
||||
mobileResult.put("saved", true);
|
||||
mobileResult.put("saveMsg", "已存在,跳过");
|
||||
processedMobiles.put(trimmedMobile, mobileResult);
|
||||
|
||||
List<LbFanManagement> existingFans = this.list(
|
||||
new LambdaQueryWrapper<LbFanManagement>()
|
||||
.eq(LbFanManagement::getTenantId, tenantId)
|
||||
.eq(LbFanManagement::getPid, existing.getId()));
|
||||
for (LbFanManagement fan : existingFans) {
|
||||
if (fan.getMobile() != null && !fan.getMobile().trim().isEmpty()) {
|
||||
nextLevelMobiles.add(fan.getMobile().trim());
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
stats.put("totalLoginAttempts", (Integer) stats.get("totalLoginAttempts") + 1);
|
||||
|
||||
HxrAdminUserLoginService.LoginApiResult loginResult =
|
||||
hxrAdminUserLoginService.login(trimmedMobile, password, loginCtx);
|
||||
|
||||
mobileResult.put("loginHttpStatus", loginResult.httpStatus());
|
||||
mobileResult.put("loginApiCode", loginResult.apiCode());
|
||||
mobileResult.put("loginApiMsg", loginResult.apiMsg());
|
||||
|
||||
if (!loginResult.success()) {
|
||||
processedMobiles.put(trimmedMobile, mobileResult);
|
||||
continue;
|
||||
}
|
||||
|
||||
mobileResult.put("loginSuccess", true);
|
||||
stats.put("totalLoginSuccess", (Integer) stats.get("totalLoginSuccess") + 1);
|
||||
|
||||
Map<String, Object> userinfo = HxrAdminUserLoginService.extractUserinfo(loginResult.parsed());
|
||||
if (userinfo == null) {
|
||||
mobileResult.put("saved", false);
|
||||
mobileResult.put("saveMsg", "响应中无 data.userinfo");
|
||||
processedMobiles.put(trimmedMobile, mobileResult);
|
||||
continue;
|
||||
}
|
||||
|
||||
Map<String, Object> saveResult = saveFanFromLoginUserinfo(userinfo, tenantId);
|
||||
mobileResult.put("saved", saveResult.get("saved"));
|
||||
mobileResult.put("saveMsg", saveResult.get("saveMsg"));
|
||||
if (Boolean.TRUE.equals(saveResult.get("saved"))) {
|
||||
stats.put("totalFansSaved", (Integer) stats.get("totalFansSaved") + 1);
|
||||
|
||||
Map<String, Object> fetchResult = fetchAndSaveFansFromThirdParty(userinfo, tenantId);
|
||||
mobileResult.put("fansFetchSuccess", fetchResult.get("fansFetchSuccess"));
|
||||
mobileResult.put("fansFetchMsg", fetchResult.get("fansFetchMsg"));
|
||||
|
||||
Integer fansFetched = (Integer) fetchResult.get("fansFetched");
|
||||
Integer fansSaved = (Integer) fetchResult.get("fansSaved");
|
||||
if (fansFetched != null) {
|
||||
mobileResult.put("fansFetched", fansFetched);
|
||||
stats.put("totalFansFetched", (Integer) stats.get("totalFansFetched") + fansFetched);
|
||||
}
|
||||
if (fansSaved != null) {
|
||||
mobileResult.put("fansSaved", fansSaved);
|
||||
stats.put("totalFansSaved", (Integer) stats.get("totalFansSaved") + fansSaved);
|
||||
}
|
||||
|
||||
Long parentId = parseLong(userinfo.get("id"));
|
||||
if (parentId != null) {
|
||||
List<LbFanManagement> fetchedFans = this.list(
|
||||
new LambdaQueryWrapper<LbFanManagement>()
|
||||
.eq(LbFanManagement::getTenantId, tenantId)
|
||||
.eq(LbFanManagement::getPid, parentId));
|
||||
for (LbFanManagement fan : fetchedFans) {
|
||||
if (fan.getMobile() != null && !fan.getMobile().trim().isEmpty()) {
|
||||
nextLevelMobiles.add(fan.getMobile().trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
processedMobiles.put(trimmedMobile, mobileResult);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.warn("递归发现粉丝异常 depth={} mobile={} tenantId={}", depth, trimmedMobile, tenantId, e);
|
||||
mobileResult.put("error", e.getMessage());
|
||||
processedMobiles.put(trimmedMobile, mobileResult);
|
||||
}
|
||||
}
|
||||
|
||||
if (!nextLevelMobiles.isEmpty()) {
|
||||
recursiveFetchLevel(nextLevelMobiles, null, tenantId, password, loginCtx,
|
||||
depth, maxDepth, skipExisting, stats, processedMobiles);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user