diff --git a/pom.xml b/pom.xml
index cd1de69..86c94cd 100644
--- a/pom.xml
+++ b/pom.xml
@@ -92,6 +92,13 @@
2.3.0
+
+
+ commons-codec
+ commons-codec
+ 1.16.1
+
+
org.springframework.boot
spring-boot-starter-test
diff --git a/src/main/java/com/gameplatform/server/controller/link/LinkController.java b/src/main/java/com/gameplatform/server/controller/link/LinkController.java
new file mode 100644
index 0000000..ef03747
--- /dev/null
+++ b/src/main/java/com/gameplatform/server/controller/link/LinkController.java
@@ -0,0 +1,48 @@
+package com.gameplatform.server.controller.link;
+
+import com.gameplatform.server.model.dto.link.LinkGenerateRequest;
+import com.gameplatform.server.model.dto.link.LinkGenerateResponse;
+import com.gameplatform.server.service.link.LinkGenerationService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.Valid;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.*;
+import reactor.core.publisher.Mono;
+
+
+@RestController
+@RequestMapping("/api/link")
+@Tag(name = "链接管理", description = "生成链接与二维码代理")
+public class LinkController {
+ private final LinkGenerationService linkGenerationService;
+
+ public LinkController(LinkGenerationService linkGenerationService) {
+ this.linkGenerationService = linkGenerationService;
+ }
+
+ @PostMapping("/generate")
+ @ResponseStatus(HttpStatus.CREATED)
+ @Operation(summary = "生成链接批次", description = "times * perTimeQuantity = 目标点数;代理需扣点,管理员不扣")
+ public Mono generate(@Valid @RequestBody LinkGenerateRequest req,
+ @RequestHeader(value = "X-Operator-Id", required = false) Long operatorId,
+ @RequestHeader(value = "X-Operator-Type", required = false) String operatorType) {
+ // 暂时用两个 Header 传操作者信息;后续接入 JWT 解析
+ Long targetAccountId = req.getAgentAccountId() != null ? req.getAgentAccountId() : operatorId;
+ return linkGenerationService.generateLinks(operatorId, safeUpper(operatorType), targetAccountId,
+ defaultInt(req.getTimes(), 0), defaultInt(req.getPerTimeQuantity(), 0))
+ .map(r -> {
+ LinkGenerateResponse resp = new LinkGenerateResponse();
+ resp.setBatchId(r.getBatchId());
+ resp.setDeductPoints(r.getNeedPoints());
+ resp.setExpireAt(r.getExpireAt());
+ resp.setCodeNos(r.getCodeNos());
+ return resp;
+ });
+ }
+
+ private static String safeUpper(String s) { return s == null ? null : s.toUpperCase(); }
+ private static int defaultInt(Integer v, int d) { return v == null ? d : v; }
+}
+
+
diff --git a/src/main/java/com/gameplatform/server/controller/link/QrProxyController.java b/src/main/java/com/gameplatform/server/controller/link/QrProxyController.java
new file mode 100644
index 0000000..6bbd39a
--- /dev/null
+++ b/src/main/java/com/gameplatform/server/controller/link/QrProxyController.java
@@ -0,0 +1,41 @@
+package com.gameplatform.server.controller.link;
+
+import com.gameplatform.server.service.external.ScriptClient;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.http.CacheControl;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import reactor.core.publisher.Mono;
+
+import java.util.concurrent.TimeUnit;
+
+@RestController
+@RequestMapping("/api/link")
+@Tag(name = "二维码代理", description = "转发脚本端的二维码图片,避免混合内容")
+public class QrProxyController {
+ private final ScriptClient scriptClient;
+
+ public QrProxyController(ScriptClient scriptClient) {
+ this.scriptClient = scriptClient;
+ }
+
+ @GetMapping(value = "/{codeNo}/qr.png", produces = MediaType.IMAGE_PNG_VALUE)
+ @Operation(summary = "二维码图片代理")
+ public Mono> qr(@PathVariable("codeNo") String codeNo) {
+ String path = "/" + codeNo + "/二维码.png";
+ return scriptClient.getQrPng(path)
+ .map(bytes -> ResponseEntity.ok()
+ .contentType(MediaType.IMAGE_PNG)
+ .cacheControl(CacheControl.maxAge(30, TimeUnit.SECONDS).cachePublic())
+ .header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=qr.png")
+ .body(bytes));
+ }
+}
+
+
diff --git a/src/main/java/com/gameplatform/server/model/dto/link/LinkGenerateRequest.java b/src/main/java/com/gameplatform/server/model/dto/link/LinkGenerateRequest.java
new file mode 100644
index 0000000..b054ce0
--- /dev/null
+++ b/src/main/java/com/gameplatform/server/model/dto/link/LinkGenerateRequest.java
@@ -0,0 +1,21 @@
+package com.gameplatform.server.model.dto.link;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+public class LinkGenerateRequest {
+ @Schema(description = "本次打脚本的次数", example = "10")
+ private Integer times;
+ @Schema(description = "每次打的数量", example = "5")
+ private Integer perTimeQuantity;
+ @Schema(description = "为哪个代理账户生成(管理员可指定,代理可省略或为自己)")
+ private Long agentAccountId;
+
+ public Integer getTimes() { return times; }
+ public void setTimes(Integer times) { this.times = times; }
+ public Integer getPerTimeQuantity() { return perTimeQuantity; }
+ public void setPerTimeQuantity(Integer perTimeQuantity) { this.perTimeQuantity = perTimeQuantity; }
+ public Long getAgentAccountId() { return agentAccountId; }
+ public void setAgentAccountId(Long agentAccountId) { this.agentAccountId = agentAccountId; }
+}
+
+
diff --git a/src/main/java/com/gameplatform/server/model/dto/link/LinkGenerateResponse.java b/src/main/java/com/gameplatform/server/model/dto/link/LinkGenerateResponse.java
new file mode 100644
index 0000000..f93adfb
--- /dev/null
+++ b/src/main/java/com/gameplatform/server/model/dto/link/LinkGenerateResponse.java
@@ -0,0 +1,28 @@
+package com.gameplatform.server.model.dto.link;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+public class LinkGenerateResponse {
+ @Schema(description = "批次ID")
+ private Long batchId;
+ @Schema(description = "扣点(管理员为0)")
+ private Long deductPoints;
+ @Schema(description = "过期时间")
+ private LocalDateTime expireAt;
+ @Schema(description = "生成的codeNo集合")
+ private List codeNos;
+
+ public Long getBatchId() { return batchId; }
+ public void setBatchId(Long batchId) { this.batchId = batchId; }
+ public Long getDeductPoints() { return deductPoints; }
+ public void setDeductPoints(Long deductPoints) { this.deductPoints = deductPoints; }
+ public LocalDateTime getExpireAt() { return expireAt; }
+ public void setExpireAt(LocalDateTime expireAt) { this.expireAt = expireAt; }
+ public List getCodeNos() { return codeNos; }
+ public void setCodeNos(List codeNos) { this.codeNos = codeNos; }
+}
+
+
diff --git a/src/main/java/com/gameplatform/server/service/external/ScriptClient.java b/src/main/java/com/gameplatform/server/service/external/ScriptClient.java
new file mode 100644
index 0000000..26230be
--- /dev/null
+++ b/src/main/java/com/gameplatform/server/service/external/ScriptClient.java
@@ -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 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 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()));
+ }
+}
+
+
diff --git a/src/main/java/com/gameplatform/server/service/link/LinkGenerationService.java b/src/main/java/com/gameplatform/server/service/link/LinkGenerationService.java
new file mode 100644
index 0000000..16751d1
--- /dev/null
+++ b/src/main/java/com/gameplatform/server/service/link/LinkGenerationService.java
@@ -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 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 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 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 getCodeNos() { return codeNos; }
+ public void setCodeNos(List codeNos) { this.codeNos = codeNos; }
+ }
+}
+
+
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index f46ffa2..d013c02 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -53,3 +53,12 @@ springdoc:
disable-swagger-default-url: true
display-request-duration: true
packages-to-scan: com.gameplatform.server.controller
+
+# 外部脚本端配置与链接过期时间
+script:
+ base-url: "http://36.138.184.60:12345"
+ connect-timeout-ms: 3000
+ read-timeout-ms: 5000
+
+link:
+ expire-hours: 2
diff --git a/target/classes/application.yml b/target/classes/application.yml
index f46ffa2..d013c02 100644
--- a/target/classes/application.yml
+++ b/target/classes/application.yml
@@ -53,3 +53,12 @@ springdoc:
disable-swagger-default-url: true
display-request-duration: true
packages-to-scan: com.gameplatform.server.controller
+
+# 外部脚本端配置与链接过期时间
+script:
+ base-url: "http://36.138.184.60:12345"
+ connect-timeout-ms: 3000
+ read-timeout-ms: 5000
+
+link:
+ expire-hours: 2
diff --git a/target/classes/com/gameplatform/server/GamePlatformServerApplication.class b/target/classes/com/gameplatform/server/GamePlatformServerApplication.class
deleted file mode 100644
index 91b7441..0000000
Binary files a/target/classes/com/gameplatform/server/GamePlatformServerApplication.class and /dev/null differ
diff --git a/target/classes/com/gameplatform/server/config/CorsConfig.class b/target/classes/com/gameplatform/server/config/CorsConfig.class
deleted file mode 100644
index 9b5d160..0000000
Binary files a/target/classes/com/gameplatform/server/config/CorsConfig.class and /dev/null differ
diff --git a/target/classes/com/gameplatform/server/config/SwaggerConfig.class b/target/classes/com/gameplatform/server/config/SwaggerConfig.class
deleted file mode 100644
index 651961f..0000000
Binary files a/target/classes/com/gameplatform/server/config/SwaggerConfig.class and /dev/null differ
diff --git a/target/classes/com/gameplatform/server/controller/UserController.class b/target/classes/com/gameplatform/server/controller/UserController.class
deleted file mode 100644
index 0c62efc..0000000
Binary files a/target/classes/com/gameplatform/server/controller/UserController.class and /dev/null differ
diff --git a/target/classes/com/gameplatform/server/controller/admin/AccountController.class b/target/classes/com/gameplatform/server/controller/admin/AccountController.class
deleted file mode 100644
index 1f54764..0000000
Binary files a/target/classes/com/gameplatform/server/controller/admin/AccountController.class and /dev/null differ
diff --git a/target/classes/com/gameplatform/server/controller/auth/AuthController$1.class b/target/classes/com/gameplatform/server/controller/auth/AuthController$1.class
deleted file mode 100644
index 542d1f9..0000000
Binary files a/target/classes/com/gameplatform/server/controller/auth/AuthController$1.class and /dev/null differ
diff --git a/target/classes/com/gameplatform/server/controller/auth/AuthController.class b/target/classes/com/gameplatform/server/controller/auth/AuthController.class
deleted file mode 100644
index da79dfb..0000000
Binary files a/target/classes/com/gameplatform/server/controller/auth/AuthController.class and /dev/null differ
diff --git a/target/classes/com/gameplatform/server/controller/link/LinkController.class b/target/classes/com/gameplatform/server/controller/link/LinkController.class
new file mode 100644
index 0000000..b04cd43
Binary files /dev/null and b/target/classes/com/gameplatform/server/controller/link/LinkController.class differ
diff --git a/target/classes/com/gameplatform/server/controller/link/QrProxyController.class b/target/classes/com/gameplatform/server/controller/link/QrProxyController.class
new file mode 100644
index 0000000..04874d1
Binary files /dev/null and b/target/classes/com/gameplatform/server/controller/link/QrProxyController.class differ
diff --git a/target/classes/com/gameplatform/server/exception/GlobalExceptionHandler$1.class b/target/classes/com/gameplatform/server/exception/GlobalExceptionHandler$1.class
deleted file mode 100644
index 315d986..0000000
Binary files a/target/classes/com/gameplatform/server/exception/GlobalExceptionHandler$1.class and /dev/null differ
diff --git a/target/classes/com/gameplatform/server/exception/GlobalExceptionHandler$2.class b/target/classes/com/gameplatform/server/exception/GlobalExceptionHandler$2.class
deleted file mode 100644
index 88214a0..0000000
Binary files a/target/classes/com/gameplatform/server/exception/GlobalExceptionHandler$2.class and /dev/null differ
diff --git a/target/classes/com/gameplatform/server/exception/GlobalExceptionHandler.class b/target/classes/com/gameplatform/server/exception/GlobalExceptionHandler.class
deleted file mode 100644
index 3ae9d57..0000000
Binary files a/target/classes/com/gameplatform/server/exception/GlobalExceptionHandler.class and /dev/null differ
diff --git a/target/classes/com/gameplatform/server/mapper/account/UserAccountMapper.class b/target/classes/com/gameplatform/server/mapper/account/UserAccountMapper.class
deleted file mode 100644
index 27e4f34..0000000
Binary files a/target/classes/com/gameplatform/server/mapper/account/UserAccountMapper.class and /dev/null differ
diff --git a/target/classes/com/gameplatform/server/mapper/admin/AnnouncementMapper.class b/target/classes/com/gameplatform/server/mapper/admin/AnnouncementMapper.class
deleted file mode 100644
index a4a44f5..0000000
Binary files a/target/classes/com/gameplatform/server/mapper/admin/AnnouncementMapper.class and /dev/null differ
diff --git a/target/classes/com/gameplatform/server/mapper/admin/OperationLogMapper.class b/target/classes/com/gameplatform/server/mapper/admin/OperationLogMapper.class
deleted file mode 100644
index 93a8f9d..0000000
Binary files a/target/classes/com/gameplatform/server/mapper/admin/OperationLogMapper.class and /dev/null differ
diff --git a/target/classes/com/gameplatform/server/mapper/agent/AgentPointsTxMapper.class b/target/classes/com/gameplatform/server/mapper/agent/AgentPointsTxMapper.class
deleted file mode 100644
index 53e1db6..0000000
Binary files a/target/classes/com/gameplatform/server/mapper/agent/AgentPointsTxMapper.class and /dev/null differ
diff --git a/target/classes/com/gameplatform/server/mapper/agent/LinkBatchMapper.class b/target/classes/com/gameplatform/server/mapper/agent/LinkBatchMapper.class
deleted file mode 100644
index 3dac129..0000000
Binary files a/target/classes/com/gameplatform/server/mapper/agent/LinkBatchMapper.class and /dev/null differ
diff --git a/target/classes/com/gameplatform/server/mapper/agent/LinkTaskMapper.class b/target/classes/com/gameplatform/server/mapper/agent/LinkTaskMapper.class
deleted file mode 100644
index 4484dab..0000000
Binary files a/target/classes/com/gameplatform/server/mapper/agent/LinkTaskMapper.class and /dev/null differ
diff --git a/target/classes/com/gameplatform/server/model/dto/account/AccountCreateRequest.class b/target/classes/com/gameplatform/server/model/dto/account/AccountCreateRequest.class
deleted file mode 100644
index a8f3453..0000000
Binary files a/target/classes/com/gameplatform/server/model/dto/account/AccountCreateRequest.class and /dev/null differ
diff --git a/target/classes/com/gameplatform/server/model/dto/account/AccountResponse.class b/target/classes/com/gameplatform/server/model/dto/account/AccountResponse.class
deleted file mode 100644
index d33af3a..0000000
Binary files a/target/classes/com/gameplatform/server/model/dto/account/AccountResponse.class and /dev/null differ
diff --git a/target/classes/com/gameplatform/server/model/dto/account/AccountUpdateRequest.class b/target/classes/com/gameplatform/server/model/dto/account/AccountUpdateRequest.class
deleted file mode 100644
index 4ee9faf..0000000
Binary files a/target/classes/com/gameplatform/server/model/dto/account/AccountUpdateRequest.class and /dev/null differ
diff --git a/target/classes/com/gameplatform/server/model/dto/account/ResetPasswordRequest.class b/target/classes/com/gameplatform/server/model/dto/account/ResetPasswordRequest.class
deleted file mode 100644
index 9d718cc..0000000
Binary files a/target/classes/com/gameplatform/server/model/dto/account/ResetPasswordRequest.class and /dev/null differ
diff --git a/target/classes/com/gameplatform/server/model/dto/auth/LoginRequest.class b/target/classes/com/gameplatform/server/model/dto/auth/LoginRequest.class
deleted file mode 100644
index bf762f2..0000000
Binary files a/target/classes/com/gameplatform/server/model/dto/auth/LoginRequest.class and /dev/null differ
diff --git a/target/classes/com/gameplatform/server/model/dto/auth/LoginResponse.class b/target/classes/com/gameplatform/server/model/dto/auth/LoginResponse.class
deleted file mode 100644
index 2557f1e..0000000
Binary files a/target/classes/com/gameplatform/server/model/dto/auth/LoginResponse.class and /dev/null differ
diff --git a/target/classes/com/gameplatform/server/model/dto/common/PageResult.class b/target/classes/com/gameplatform/server/model/dto/common/PageResult.class
deleted file mode 100644
index e70f530..0000000
Binary files a/target/classes/com/gameplatform/server/model/dto/common/PageResult.class and /dev/null differ
diff --git a/target/classes/com/gameplatform/server/model/dto/link/LinkGenerateRequest.class b/target/classes/com/gameplatform/server/model/dto/link/LinkGenerateRequest.class
new file mode 100644
index 0000000..19260ea
Binary files /dev/null and b/target/classes/com/gameplatform/server/model/dto/link/LinkGenerateRequest.class differ
diff --git a/target/classes/com/gameplatform/server/model/dto/link/LinkGenerateResponse.class b/target/classes/com/gameplatform/server/model/dto/link/LinkGenerateResponse.class
new file mode 100644
index 0000000..b0f2dd8
Binary files /dev/null and b/target/classes/com/gameplatform/server/model/dto/link/LinkGenerateResponse.class differ
diff --git a/target/classes/com/gameplatform/server/model/entity/account/UserAccount.class b/target/classes/com/gameplatform/server/model/entity/account/UserAccount.class
deleted file mode 100644
index 0670941..0000000
Binary files a/target/classes/com/gameplatform/server/model/entity/account/UserAccount.class and /dev/null differ
diff --git a/target/classes/com/gameplatform/server/model/entity/admin/Announcement.class b/target/classes/com/gameplatform/server/model/entity/admin/Announcement.class
deleted file mode 100644
index cd8f533..0000000
Binary files a/target/classes/com/gameplatform/server/model/entity/admin/Announcement.class and /dev/null differ
diff --git a/target/classes/com/gameplatform/server/model/entity/admin/OperationLog.class b/target/classes/com/gameplatform/server/model/entity/admin/OperationLog.class
deleted file mode 100644
index 9679548..0000000
Binary files a/target/classes/com/gameplatform/server/model/entity/admin/OperationLog.class and /dev/null differ
diff --git a/target/classes/com/gameplatform/server/model/entity/agent/AgentPointsTx.class b/target/classes/com/gameplatform/server/model/entity/agent/AgentPointsTx.class
deleted file mode 100644
index a2d34b7..0000000
Binary files a/target/classes/com/gameplatform/server/model/entity/agent/AgentPointsTx.class and /dev/null differ
diff --git a/target/classes/com/gameplatform/server/model/entity/agent/LinkBatch.class b/target/classes/com/gameplatform/server/model/entity/agent/LinkBatch.class
deleted file mode 100644
index 792655d..0000000
Binary files a/target/classes/com/gameplatform/server/model/entity/agent/LinkBatch.class and /dev/null differ
diff --git a/target/classes/com/gameplatform/server/model/entity/agent/LinkTask.class b/target/classes/com/gameplatform/server/model/entity/agent/LinkTask.class
deleted file mode 100644
index 454a491..0000000
Binary files a/target/classes/com/gameplatform/server/model/entity/agent/LinkTask.class and /dev/null differ
diff --git a/target/classes/com/gameplatform/server/security/JwtService.class b/target/classes/com/gameplatform/server/security/JwtService.class
deleted file mode 100644
index eadb048..0000000
Binary files a/target/classes/com/gameplatform/server/security/JwtService.class and /dev/null differ
diff --git a/target/classes/com/gameplatform/server/security/SecurityConfig.class b/target/classes/com/gameplatform/server/security/SecurityConfig.class
deleted file mode 100644
index 6242344..0000000
Binary files a/target/classes/com/gameplatform/server/security/SecurityConfig.class and /dev/null differ
diff --git a/target/classes/com/gameplatform/server/service/UserService.class b/target/classes/com/gameplatform/server/service/UserService.class
deleted file mode 100644
index a757692..0000000
Binary files a/target/classes/com/gameplatform/server/service/UserService.class and /dev/null differ
diff --git a/target/classes/com/gameplatform/server/service/account/AccountService.class b/target/classes/com/gameplatform/server/service/account/AccountService.class
deleted file mode 100644
index 914329f..0000000
Binary files a/target/classes/com/gameplatform/server/service/account/AccountService.class and /dev/null differ
diff --git a/target/classes/com/gameplatform/server/service/auth/AuthService.class b/target/classes/com/gameplatform/server/service/auth/AuthService.class
deleted file mode 100644
index 7324f12..0000000
Binary files a/target/classes/com/gameplatform/server/service/auth/AuthService.class and /dev/null differ
diff --git a/target/classes/com/gameplatform/server/service/external/ScriptClient.class b/target/classes/com/gameplatform/server/service/external/ScriptClient.class
new file mode 100644
index 0000000..67256bc
Binary files /dev/null and b/target/classes/com/gameplatform/server/service/external/ScriptClient.class differ
diff --git a/target/classes/com/gameplatform/server/service/link/LinkGenerationService$GenerateResult.class b/target/classes/com/gameplatform/server/service/link/LinkGenerationService$GenerateResult.class
new file mode 100644
index 0000000..e9e11e1
Binary files /dev/null and b/target/classes/com/gameplatform/server/service/link/LinkGenerationService$GenerateResult.class differ
diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
index ba69f71..7675023 100644
--- a/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
+++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
@@ -1,34 +1,45 @@
com\gameplatform\server\mapper\agent\LinkTaskMapper.class
com\gameplatform\server\exception\GlobalExceptionHandler$2.class
-com\gameplatform\server\model\entity\admin\Announcement.class
+com\gameplatform\server\service\link\LinkGenerationService.class
com\gameplatform\server\mapper\agent\AgentPointsTxMapper.class
com\gameplatform\server\config\CorsConfig.class
com\gameplatform\server\exception\GlobalExceptionHandler.class
com\gameplatform\server\model\dto\common\PageResult.class
com\gameplatform\server\service\UserService.class
com\gameplatform\server\controller\auth\AuthController.class
-com\gameplatform\server\model\entity\agent\AgentPointsTx.class
-com\gameplatform\server\controller\admin\AccountController.class
com\gameplatform\server\GamePlatformServerApplication.class
-com\gameplatform\server\model\dto\auth\LoginRequest.class
-com\gameplatform\server\model\entity\account\UserAccount.class
com\gameplatform\server\mapper\admin\AnnouncementMapper.class
-com\gameplatform\server\model\dto\auth\LoginResponse.class
-com\gameplatform\server\model\entity\agent\LinkTask.class
+com\gameplatform\server\model\dto\link\CreateLinkBatchResponse.class
com\gameplatform\server\service\auth\AuthService.class
com\gameplatform\server\config\SwaggerConfig.class
-com\gameplatform\server\exception\GlobalExceptionHandler$1.class
com\gameplatform\server\mapper\account\UserAccountMapper.class
+com\gameplatform\server\service\account\AccountService.class
+com\gameplatform\server\model\dto\link\CreateLinkBatchRequest.class
+com\gameplatform\server\model\dto\link\LinkStatisticsResponse.class
+com\gameplatform\server\service\link\QrCodeProxyService$CachedImage.class
+com\gameplatform\server\service\link\QrCodeProxyService.class
+com\gameplatform\server\controller\auth\AuthController$1.class
+com\gameplatform\server\model\entity\admin\Announcement.class
+com\gameplatform\server\controller\link\LinkTaskController.class
+com\gameplatform\server\model\dto\link\LinkTaskResponse.class
+com\gameplatform\server\model\entity\agent\AgentPointsTx.class
+com\gameplatform\server\controller\admin\AccountController.class
+com\gameplatform\server\model\dto\auth\LoginRequest.class
+com\gameplatform\server\model\entity\account\UserAccount.class
+com\gameplatform\server\model\dto\auth\LoginResponse.class
+com\gameplatform\server\model\entity\agent\LinkTask.class
+com\gameplatform\server\exception\GlobalExceptionHandler$1.class
com\gameplatform\server\mapper\agent\LinkBatchMapper.class
com\gameplatform\server\security\JwtService.class
+com\gameplatform\server\model\dto\link\LinkTaskQueryRequest.class
com\gameplatform\server\mapper\admin\OperationLogMapper.class
com\gameplatform\server\model\entity\agent\LinkBatch.class
-com\gameplatform\server\service\account\AccountService.class
com\gameplatform\server\controller\UserController.class
com\gameplatform\server\security\SecurityConfig.class
+com\gameplatform\server\service\link\LinkTaskService.class
com\gameplatform\server\model\entity\admin\OperationLog.class
com\gameplatform\server\model\dto\account\AccountResponse.class
com\gameplatform\server\model\dto\account\AccountCreateRequest.class
+com\gameplatform\server\config\ScheduleConfig.class
com\gameplatform\server\model\dto\account\AccountUpdateRequest.class
-com\gameplatform\server\controller\auth\AuthController$1.class
com\gameplatform\server\model\dto\account\ResetPasswordRequest.class
diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
index 7079876..baef7be 100644
--- a/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
+++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
@@ -6,8 +6,10 @@ D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\service\acc
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\service\auth\AuthService.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\model\dto\auth\LoginRequest.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\controller\admin\AccountController.java
+D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\service\external\ScriptClient.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\controller\UserController.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\mapper\agent\LinkBatchMapper.java
+D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\controller\link\QrProxyController.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\mapper\agent\LinkTaskMapper.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\config\SwaggerConfig.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\model\dto\account\AccountResponse.java
@@ -17,13 +19,17 @@ D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\controller\
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\security\JwtService.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\config\CorsConfig.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\model\dto\common\PageResult.java
+D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\model\dto\link\LinkGenerateResponse.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\GamePlatformServerApplication.java
+D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\model\dto\link\LinkGenerateRequest.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\model\entity\agent\LinkTask.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\model\dto\account\AccountUpdateRequest.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\model\entity\admin\Announcement.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\model\entity\agent\AgentPointsTx.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\model\dto\auth\LoginResponse.java
+D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\controller\link\LinkController.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\mapper\admin\OperationLogMapper.java
+D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\service\link\LinkGenerationService.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\exception\GlobalExceptionHandler.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\model\entity\agent\LinkBatch.java
D:\project\gamePlatform\server\src\main\java\com\gameplatform\server\mapper\account\UserAccountMapper.java
diff --git a/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst b/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst
new file mode 100644
index 0000000..f558db7
--- /dev/null
+++ b/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst
@@ -0,0 +1,2 @@
+com\gameplatform\server\service\link\LinkGenerationServiceTest.class
+com\gameplatform\server\service\link\LinkTaskServiceTest.class
diff --git a/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst b/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst
new file mode 100644
index 0000000..8743795
--- /dev/null
+++ b/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst
@@ -0,0 +1,2 @@
+D:\project\gamePlatform\server\src\test\java\com\gameplatform\server\service\link\LinkTaskServiceTest.java
+D:\project\gamePlatform\server\src\test\java\com\gameplatform\server\service\link\LinkGenerationServiceTest.java