添加Apache Commons Codec依赖以支持SHA-256和十六进制工具,并在application.yml中新增外部脚本配置及链接过期时间设置。同时,删除不再使用的类文件以清理项目结构。
This commit is contained in:
60
src/main/java/com/gameplatform/server/service/external/ScriptClient.java
vendored
Normal file
60
src/main/java/com/gameplatform/server/service/external/ScriptClient.java
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
package com.gameplatform.server.service.external;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.reactive.function.client.ExchangeStrategies;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
@Service
|
||||
public class ScriptClient {
|
||||
private static final Logger log = LoggerFactory.getLogger(ScriptClient.class);
|
||||
|
||||
private final WebClient webClient;
|
||||
private final String baseUrl;
|
||||
|
||||
public ScriptClient(
|
||||
@Value("${script.base-url}") String baseUrl,
|
||||
@Value("${script.connect-timeout-ms:3000}") int connectTimeoutMs,
|
||||
@Value("${script.read-timeout-ms:5000}") int readTimeoutMs
|
||||
) {
|
||||
this.baseUrl = baseUrl;
|
||||
this.webClient = WebClient.builder()
|
||||
.baseUrl(baseUrl)
|
||||
.exchangeStrategies(ExchangeStrategies.builder()
|
||||
.codecs(cfg -> cfg.defaultCodecs().maxInMemorySize(2 * 1024 * 1024))
|
||||
.build())
|
||||
.build();
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("ScriptClient initialized baseUrl={}, connectTimeoutMs={}, readTimeoutMs={}", this.baseUrl, connectTimeoutMs, readTimeoutMs);
|
||||
}
|
||||
}
|
||||
|
||||
public Mono<byte[]> getQrPng(String path) {
|
||||
// path example: /{codeNo}/二维码.png
|
||||
return webClient.get()
|
||||
.uri(path)
|
||||
.accept(MediaType.IMAGE_PNG)
|
||||
.retrieve()
|
||||
.bodyToMono(byte[].class)
|
||||
.timeout(Duration.ofSeconds(5))
|
||||
.doOnError(e -> log.warn("ScriptClient.getQrPng error path={} err={}", path, e.toString()));
|
||||
}
|
||||
|
||||
public Mono<String> getText(String path) {
|
||||
return webClient.get()
|
||||
.uri(path)
|
||||
.accept(MediaType.TEXT_PLAIN)
|
||||
.retrieve()
|
||||
.bodyToMono(String.class)
|
||||
.timeout(Duration.ofSeconds(5))
|
||||
.doOnError(e -> log.warn("ScriptClient.getText error path={} err={}", path, e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
package com.gameplatform.server.service.link;
|
||||
|
||||
import com.gameplatform.server.mapper.account.UserAccountMapper;
|
||||
import com.gameplatform.server.mapper.agent.AgentPointsTxMapper;
|
||||
import com.gameplatform.server.mapper.agent.LinkBatchMapper;
|
||||
import com.gameplatform.server.mapper.agent.LinkTaskMapper;
|
||||
import com.gameplatform.server.model.entity.account.UserAccount;
|
||||
import com.gameplatform.server.model.entity.agent.AgentPointsTx;
|
||||
import com.gameplatform.server.model.entity.agent.LinkBatch;
|
||||
import com.gameplatform.server.model.entity.agent.LinkTask;
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class LinkGenerationService {
|
||||
private static final Logger log = LoggerFactory.getLogger(LinkGenerationService.class);
|
||||
|
||||
private final UserAccountMapper userAccountMapper;
|
||||
private final LinkBatchMapper linkBatchMapper;
|
||||
private final LinkTaskMapper linkTaskMapper;
|
||||
private final AgentPointsTxMapper agentPointsTxMapper;
|
||||
private final int expireHours;
|
||||
|
||||
public LinkGenerationService(UserAccountMapper userAccountMapper,
|
||||
LinkBatchMapper linkBatchMapper,
|
||||
LinkTaskMapper linkTaskMapper,
|
||||
AgentPointsTxMapper agentPointsTxMapper,
|
||||
@Value("${link.expire-hours:2}") int expireHours) {
|
||||
this.userAccountMapper = userAccountMapper;
|
||||
this.linkBatchMapper = linkBatchMapper;
|
||||
this.linkTaskMapper = linkTaskMapper;
|
||||
this.agentPointsTxMapper = agentPointsTxMapper;
|
||||
this.expireHours = expireHours;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Mono<GenerateResult> generateLinks(Long operatorId, String operatorType,
|
||||
Long targetAccountId,
|
||||
int times, int perTimeQuantity) {
|
||||
return Mono.fromCallable(() -> doGenerate(operatorId, operatorType, targetAccountId, times, perTimeQuantity))
|
||||
.subscribeOn(Schedulers.boundedElastic());
|
||||
}
|
||||
|
||||
private GenerateResult doGenerate(Long operatorId, String operatorType,
|
||||
Long targetAccountId,
|
||||
int times, int perTimeQuantity) {
|
||||
if (times <= 0 || perTimeQuantity <= 0) {
|
||||
throw new IllegalArgumentException("times 与 perTimeQuantity 必须为正整数");
|
||||
}
|
||||
|
||||
UserAccount target = userAccountMapper.findById(targetAccountId);
|
||||
if (target == null) {
|
||||
throw new IllegalArgumentException("目标账户不存在");
|
||||
}
|
||||
boolean isAdminOperator = "ADMIN".equalsIgnoreCase(operatorType);
|
||||
if (!isAdminOperator && !"AGENT".equalsIgnoreCase(operatorType)) {
|
||||
throw new IllegalArgumentException("非法操作者类型");
|
||||
}
|
||||
if (!"AGENT".equalsIgnoreCase(target.getUserType())) {
|
||||
throw new IllegalArgumentException("仅支持为代理账户生成链接");
|
||||
}
|
||||
|
||||
long needPoints = (long) times * (long) perTimeQuantity;
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("generateLinks operatorId={} operatorType={} targetAccountId={} times={} perTimeQuantity={} needPoints={} expireHours={}",
|
||||
operatorId, operatorType, targetAccountId, times, perTimeQuantity, needPoints, expireHours);
|
||||
}
|
||||
if (!isAdminOperator) {
|
||||
// 代理商自操作,需扣点判断
|
||||
long balance = target.getPointsBalance() == null ? 0L : target.getPointsBalance();
|
||||
if (balance < needPoints) {
|
||||
throw new IllegalStateException("点数不足");
|
||||
}
|
||||
}
|
||||
|
||||
LinkBatch batch = new LinkBatch();
|
||||
batch.setAgentId(target.getId());
|
||||
batch.setQuantity(times);
|
||||
batch.setTimes(times);
|
||||
batch.setBatchSize(perTimeQuantity);
|
||||
batch.setDeductPoints(needPoints);
|
||||
batch.setOperatorId(operatorId);
|
||||
linkBatchMapper.insert(batch);
|
||||
|
||||
LocalDateTime expireAt = LocalDateTime.now().plusHours(expireHours);
|
||||
List<LinkTask> tasks = new ArrayList<>();
|
||||
for (int i = 0; i < times; i++) {
|
||||
LinkTask t = new LinkTask();
|
||||
t.setBatchId(batch.getId());
|
||||
t.setAgentId(target.getId());
|
||||
t.setCodeNo(generateCodeNo());
|
||||
t.setTokenHash(DigestUtils.sha256Hex(generateToken()));
|
||||
t.setExpireAt(expireAt);
|
||||
t.setStatus("NEW");
|
||||
linkTaskMapper.insert(t);
|
||||
tasks.add(t);
|
||||
}
|
||||
|
||||
if (!isAdminOperator) {
|
||||
// 扣点流水 + 账户余额
|
||||
long before = target.getPointsBalance() == null ? 0L : target.getPointsBalance();
|
||||
long after = before - needPoints;
|
||||
|
||||
AgentPointsTx tx = new AgentPointsTx();
|
||||
tx.setAccountId(target.getId());
|
||||
tx.setType("DEDUCT");
|
||||
tx.setBeforePoints(before);
|
||||
tx.setDeltaPoints(-needPoints);
|
||||
tx.setAfterPoints(after);
|
||||
tx.setReason("create_links");
|
||||
tx.setRefId(batch.getId());
|
||||
tx.setOperatorId(operatorId);
|
||||
agentPointsTxMapper.insert(tx);
|
||||
|
||||
UserAccount patch = new UserAccount();
|
||||
patch.setId(target.getId());
|
||||
patch.setPointsBalance(after);
|
||||
userAccountMapper.update(patch);
|
||||
}
|
||||
|
||||
GenerateResult result = new GenerateResult();
|
||||
result.setBatchId(batch.getId());
|
||||
result.setNeedPoints(needPoints);
|
||||
result.setExpireAt(expireAt);
|
||||
result.setCodeNos(tasks.stream().map(LinkTask::getCodeNo).toList());
|
||||
return result;
|
||||
}
|
||||
|
||||
private static final String CODE_CHARS = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"; // avoid confusing chars
|
||||
private static final SecureRandom RANDOM = new SecureRandom();
|
||||
|
||||
private String generateCodeNo() {
|
||||
StringBuilder sb = new StringBuilder(8);
|
||||
for (int i = 0; i < 8; i++) {
|
||||
sb.append(CODE_CHARS.charAt(RANDOM.nextInt(CODE_CHARS.length())));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private String generateToken() {
|
||||
byte[] bytes = new byte[32];
|
||||
RANDOM.nextBytes(bytes);
|
||||
return org.apache.commons.codec.binary.Hex.encodeHexString(bytes);
|
||||
}
|
||||
|
||||
public static class GenerateResult {
|
||||
private Long batchId;
|
||||
private Long needPoints;
|
||||
private LocalDateTime expireAt;
|
||||
private List<String> codeNos;
|
||||
|
||||
public Long getBatchId() { return batchId; }
|
||||
public void setBatchId(Long batchId) { this.batchId = batchId; }
|
||||
public Long getNeedPoints() { return needPoints; }
|
||||
public void setNeedPoints(Long needPoints) { this.needPoints = needPoints; }
|
||||
public LocalDateTime getExpireAt() { return expireAt; }
|
||||
public void setExpireAt(LocalDateTime expireAt) { this.expireAt = expireAt; }
|
||||
public List<String> getCodeNos() { return codeNos; }
|
||||
public void setCodeNos(List<String> codeNos) { this.codeNos = codeNos; }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user