Files
smartDriveEE/src/main/java/com/rj/service/HxrAdminUserService.java
2026-05-22 22:49:06 +08:00

312 lines
13 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.rj.service;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.rj.config.HxrAdminProperties;
import com.rj.dto.hxr.HxrLbUserSelectResponse;
import com.rj.dto.hxr.HxrSimpleApiResponse;
import com.rj.dto.hxr.HxrUserRow;
import com.rj.dto.hxr.HxrUserSelectResponse;
import com.rj.util.IsoWorkdayUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.util.UriComponentsBuilder;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Optional;
/**
* 调用 hxrd 后台用户查询、更新接口(与 {@link HxrAdminOrderSelectService} 共用 {@link HxrAdminProperties} 会话)。
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class HxrAdminUserService {
/** 与后台 user/select 默认 limit 一致,用于判断是否还有下一页 */
public static final int USER_SELECT_PAGE_SIZE = 90;
private static final ObjectMapper JSON = new ObjectMapper()
.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
private static final ObjectMapper USER_JSON = new ObjectMapper()
.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.registerModule(new JavaTimeModule())
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
private static final DateTimeFormatter VIP_TIME_FORMAT =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private final HxrAdminProperties properties;
/**
* VIP 到期时间:以 {@code applyDate} 为起算日,若当天为周六、周日则先顺延到下一工作日,再顺延 2 个工作日;
* 时刻与调用时「当前时刻」的时分秒相同(仅调整日期)。用于开通同事特权。
*/
public static LocalDateTime computeVipExpireTime(LocalDate applyDate) {
return computeVipExpireTime(applyDate, 2);
}
/**
* VIP 到期时间:以 {@code applyDate} 为起算日,若当天为周六、周日则先顺延到下一工作日,
* 再顺延 {@code workdaysAfterAlign} 个工作日;时刻与当前时分秒一致。
*/
public static LocalDateTime computeVipExpireTime(LocalDate applyDate, int workdaysAfterAlign) {
if (applyDate == null) {
throw new IllegalArgumentException("applyDate不能为空");
}
if (workdaysAfterAlign < 0) {
throw new IllegalArgumentException("workdaysAfterAlign不能为负");
}
LocalDateTime now = LocalDateTime.now();
LocalDate d = applyDate;
while (IsoWorkdayUtils.isWeekend(d)) {
d = d.plusDays(1);
}
for (int i = 0; i < workdaysAfterAlign; i++) {
d = d.plusDays(1);
while (IsoWorkdayUtils.isWeekend(d)) {
d = d.plusDays(1);
}
}
return LocalDateTime.of(d, now.toLocalTime());
}
/**
* VIP 到期时间:以 {@code applyDate} 为起算日顺延 {@code calendarDays} 个自然日,时刻与当前时分秒一致。
*/
public static LocalDateTime computeVipExpireTimePlusCalendarDays(LocalDate applyDate, int calendarDays) {
if (applyDate == null) {
throw new IllegalArgumentException("applyDate不能为空");
}
if (calendarDays < 0) {
throw new IllegalArgumentException("calendarDays不能为负");
}
LocalDateTime now = LocalDateTime.now();
return LocalDateTime.of(applyDate.plusDays(calendarDays), now.toLocalTime());
}
/**
* 分页拉取用户列表,{@code data} 解析为 {@link }(与 lb_user 字段对应)。
*
* @param page 页码,从 1 开始
*/
public Optional<HxrLbUserSelectResponse> fetchUserSelectPage(int page) throws Exception {
String cookieHeader = properties.resolveCookieHeader();
if (cookieHeader == null || cookieHeader.isBlank()) {
log.warn("hxr.admin 未配置 cookie 或 phpsid跳过 user/select");
return Optional.empty();
}
String uri = UriComponentsBuilder.fromUriString(properties.getUserSelectUrl())
.replaceQueryParam("page", Math.max(1, page))
.replaceQueryParam("limit", USER_SELECT_PAGE_SIZE)
.replaceQueryParam("mobile", "")
.replaceQueryParam("id", "")
.replaceQueryParam("pid", "")
.build()
.encode()
.toUriString();
HttpClient client = HttpClient.newBuilder()
.connectTimeout(java.time.Duration.ofSeconds(35))
.followRedirects(HttpClient.Redirect.NORMAL)
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(uri))
.timeout(java.time.Duration.ofSeconds(120))
.header("Accept", "application/json, text/javascript, */*; q=0.01")
.header("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6")
.header("Cookie", cookieHeader)
.header("Priority", "u=1, i")
.header("Referer", "https://hxrdhoutai.hxrdsm.cn/app/admin/user/index")
.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36 Edg/148.0.0.0")
.GET()
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
int status = response.statusCode();
if (status < 200 || status >= 300) {
log.warn("hxr admin user/select HTTP {} page={} bodyPrefix={}",
status, page, abbreviate(response.body(), 400));
return Optional.empty();
}
HxrLbUserSelectResponse body = USER_JSON.readValue(response.body(), HxrLbUserSelectResponse.class);
if (body.code() != 0) {
log.warn("hxr admin user/select api code={} msg={} page={}", body.code(), body.msg(), page);
return Optional.empty();
}
return Optional.of(body);
}
/**
* 调用第三方接口
* 按手机号查询用户列表,返回第一条。
*/
public Optional<HxrUserRow> fetchFirstUserByMobile(String mobile) throws Exception {
String cookieHeader = properties.resolveCookieHeader();
if (cookieHeader == null || cookieHeader.isBlank()) {
log.warn("hxr.admin 未配置 cookie 或 phpsid跳过 user/select");
return Optional.empty();
}
if (mobile == null || mobile.isBlank()) {
return Optional.empty();
}
String uri = UriComponentsBuilder.fromUriString(properties.getUserSelectUrl())
.replaceQueryParam("mobile", mobile.trim())
.replaceQueryParam("id", "")
.replaceQueryParam("pid", "")
.build()
.encode()
.toUriString();
HttpClient client = HttpClient.newBuilder()
.connectTimeout(java.time.Duration.ofSeconds(35))
.followRedirects(HttpClient.Redirect.NORMAL)
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(uri))
.timeout(java.time.Duration.ofSeconds(120))
.header("Accept", "application/json, text/javascript, */*; q=0.01")
.header("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6")
.header("Cookie", cookieHeader)
.header("Priority", "u=1, i")
.header("Referer", "https://hxrdhoutai.hxrdsm.cn/app/admin/user/index")
.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36 Edg/148.0.0.0")
.GET()
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
int status = response.statusCode();
if (status < 200 || status >= 300) {
log.warn("hxr admin user/select HTTP {} mobile={} bodyPrefix={}",
status, mobile, abbreviate(response.body(), 400));
return Optional.empty();
}
HxrUserSelectResponse body = JSON.readValue(response.body(), HxrUserSelectResponse.class);
if (body.code() != 0) {
log.warn("hxr admin user/select api code={} msg={} mobile={}", body.code(), body.msg(), mobile);
return Optional.empty();
}
List<HxrUserRow> data = body.data();
if (data == null || data.isEmpty()) {
return Optional.empty();
}
HxrUserRow first = data.get(0);
if (first.id() == null) {
return Optional.empty();
}
return Optional.of(first);
}
/**
* 更新用户 VIP 相关字段({@code application/x-www-form-urlencoded})。
*
* @return 接口 {@code code==0} 时为 true
*/
public boolean updateUserVipFields(long userId, int maxOrder, String viptimeFormatted) throws Exception {
return updateUserVipFields(userId, maxOrder, viptimeFormatted, 1);
}
/**
* 更新用户 VIP 相关字段({@code application/x-www-form-urlencoded})。
*
* @param maxOrder 为 null 时不传 max_order第三方保持原值
* @param isVip 1 开通特权0 关闭特权
* @return 接口 {@code code==0} 时为 true
*/
public boolean updateUserVipFields(long userId, Integer maxOrder, String viptimeFormatted, int isVip)
throws Exception {
String cookieHeader = properties.resolveCookieHeader();
if (cookieHeader == null || cookieHeader.isBlank()) {
log.warn("hxr.admin 未配置 cookie 或 phpsid跳过 user/update");
return false;
}
String form = buildUpdateFormBody(userId, maxOrder, viptimeFormatted, isVip);
HttpClient client = HttpClient.newBuilder()
.connectTimeout(java.time.Duration.ofSeconds(35))
.followRedirects(HttpClient.Redirect.NORMAL)
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(properties.getUserUpdateUrl()))
.timeout(java.time.Duration.ofSeconds(120))
.header("Accept", "application/json, text/javascript, */*; q=0.01")
.header("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
.header("Cookie", cookieHeader)
.header("Referer", "https://hxrdhoutai.hxrdsm.cn/app/admin/user/index")
.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36 Edg/148.0.0.0")
.POST(HttpRequest.BodyPublishers.ofString(form, StandardCharsets.UTF_8))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
int status = response.statusCode();
if (status < 200 || status >= 300) {
log.warn("hxr admin user/update HTTP {} id={} bodyPrefix={}",
status, userId, abbreviate(response.body(), 400));
return false;
}
try {
HxrSimpleApiResponse api = JSON.readValue(response.body(), HxrSimpleApiResponse.class);
if (api.code() != 0) {
log.warn("hxr admin user/update api code={} msg={} id={}", api.code(), api.msg(), userId);
return false;
}
return true;
} catch (Exception e) {
log.warn("hxr admin user/update parse body id={} err={} bodyPrefix={}",
userId, e.getMessage(), abbreviate(response.body(), 400));
return false;
}
}
public static String formatVipTime(LocalDateTime t) {
return t.format(VIP_TIME_FORMAT);
}
private static String buildUpdateFormBody(long userId, Integer maxOrder, String viptimeFormatted, int isVip) {
int vipFlag = isVip == 1 ? 1 : 0;
String viptime = viptimeFormatted != null ? viptimeFormatted : "";
StringBuilder form = new StringBuilder();
form.append("id=").append(userId);
if (maxOrder != null) {
form.append("&max_order=").append(maxOrder);
}
form.append("&is_vip=").append(vipFlag)
.append("&is_resell=1")
.append("&status=1")
.append("&viptime=").append(URLEncoder.encode(viptime, StandardCharsets.UTF_8));
return form.toString();
}
private static String abbreviate(String s, int maxLen) {
if (s == null) {
return "";
}
return s.length() <= maxLen ? s : s.substring(0, maxLen) + "...";
}
}