init
This commit is contained in:
parent
729fad3615
commit
72e067c9c1
@ -31,15 +31,14 @@ import java.util.stream.Stream;
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping
|
||||
public class AdminController {
|
||||
public class AdminController extends BaseController {
|
||||
|
||||
private final UserRepository userRepository;
|
||||
private final CookieRepository cookieRepository;
|
||||
private final OrderRepository orderRepository;
|
||||
private final CardRepository cardRepository;
|
||||
private final KeywordRepository keywordRepository;
|
||||
private final TokenService tokenService;
|
||||
|
||||
|
||||
// Log directory - adjust as needed for migration context
|
||||
private final String LOG_DIR = "logs";
|
||||
|
||||
@ -50,12 +49,12 @@ public class AdminController {
|
||||
CardRepository cardRepository,
|
||||
KeywordRepository keywordRepository,
|
||||
TokenService tokenService) {
|
||||
super(tokenService);
|
||||
this.userRepository = userRepository;
|
||||
this.cookieRepository = cookieRepository;
|
||||
this.orderRepository = orderRepository;
|
||||
this.cardRepository = cardRepository;
|
||||
this.keywordRepository = keywordRepository;
|
||||
this.tokenService = tokenService;
|
||||
}
|
||||
|
||||
// ------------------------- User Management -------------------------
|
||||
|
||||
@ -17,18 +17,18 @@ import java.util.Map;
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping
|
||||
public class AuthController {
|
||||
public class AuthController extends BaseController {
|
||||
|
||||
private final AuthService authService;
|
||||
private final TokenService tokenService;
|
||||
|
||||
|
||||
private static final String ADMIN_USERNAME = "admin";
|
||||
private static final String DEFAULT_ADMIN_PASSWORD = "admin123";
|
||||
|
||||
@Autowired
|
||||
public AuthController(AuthService authService, TokenService tokenService) {
|
||||
public AuthController(AuthService authService,
|
||||
TokenService tokenService) {
|
||||
super(tokenService);
|
||||
this.authService = authService;
|
||||
this.tokenService = tokenService;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -69,7 +69,7 @@ public class AuthController {
|
||||
loginType = "用户名/密码";
|
||||
log.info("【{}】尝试用户名登录", request.getUsername());
|
||||
user = authService.verifyUserPassword(request.getUsername(), request.getPassword());
|
||||
}
|
||||
}
|
||||
// 2. 邮箱/密码登录
|
||||
else if (StrUtil.isNotBlank(request.getEmail()) && StrUtil.isNotBlank(request.getPassword())) {
|
||||
loginType = "邮箱/密码";
|
||||
@ -95,15 +95,15 @@ public class AuthController {
|
||||
if (user != null) {
|
||||
boolean isAdmin = ADMIN_USERNAME.equals(user.getUsername());
|
||||
String token = tokenService.generateToken(user, isAdmin);
|
||||
|
||||
|
||||
log.info("【{}#{}】{}登录成功{}", user.getUsername(), user.getId(), loginType, isAdmin ? "(管理员)" : "");
|
||||
|
||||
|
||||
return new LoginResponse(true, token, "登录成功", user.getId(), user.getUsername(), isAdmin);
|
||||
} else {
|
||||
log.warn("{}登录失败", loginType);
|
||||
if (loginType.contains("验证码")) {
|
||||
// 这个分支其实上面已经处理了,这里是兜底逻辑
|
||||
return new LoginResponse(false, "用户不存在");
|
||||
return new LoginResponse(false, "用户不存在");
|
||||
}
|
||||
return new LoginResponse(false, "用户名或密码错误"); // 或邮箱或密码错误
|
||||
}
|
||||
@ -117,7 +117,7 @@ public class AuthController {
|
||||
public Map<String, Object> verify(HttpServletRequest request) {
|
||||
String token = getTokenFromRequest(request);
|
||||
TokenService.TokenInfo info = tokenService.verifyToken(token);
|
||||
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
if (info != null) {
|
||||
response.put("authenticated", true);
|
||||
@ -142,7 +142,7 @@ public class AuthController {
|
||||
response.put("message", "已登出");
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 修改管理员密码接口
|
||||
* 对应 Python: /change-admin-password
|
||||
@ -151,12 +151,12 @@ public class AuthController {
|
||||
public Map<String, Object> changeAdminPassword(@RequestBody ChangePasswordRequest request, HttpServletRequest httpRequest) {
|
||||
String token = getTokenFromRequest(httpRequest);
|
||||
TokenService.TokenInfo info = tokenService.verifyToken(token);
|
||||
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
if (info == null || !info.isAdmin) {
|
||||
response.put("success", false);
|
||||
response.put("message", "未授权访问或非管理员");
|
||||
return response;
|
||||
response.put("success", false);
|
||||
response.put("message", "未授权访问或非管理员");
|
||||
return response;
|
||||
}
|
||||
|
||||
User user = authService.verifyUserPassword(ADMIN_USERNAME, request.getCurrent_password());
|
||||
@ -177,7 +177,7 @@ public class AuthController {
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 普通用户修改密码接口
|
||||
* 对应 Python: /change-password
|
||||
@ -186,19 +186,19 @@ public class AuthController {
|
||||
public Map<String, Object> changeUserPassword(@RequestBody ChangePasswordRequest request, HttpServletRequest httpRequest) {
|
||||
String token = getTokenFromRequest(httpRequest);
|
||||
TokenService.TokenInfo info = tokenService.verifyToken(token);
|
||||
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
if (info == null) {
|
||||
response.put("success", false);
|
||||
response.put("message", "无法获取用户信息");
|
||||
return response;
|
||||
response.put("success", false);
|
||||
response.put("message", "无法获取用户信息");
|
||||
return response;
|
||||
}
|
||||
|
||||
User user = authService.verifyUserPassword(info.username, request.getCurrent_password());
|
||||
if (user == null) {
|
||||
response.put("success", false);
|
||||
response.put("message", "当前密码错误");
|
||||
return response;
|
||||
response.put("success", false);
|
||||
response.put("message", "当前密码错误");
|
||||
return response;
|
||||
}
|
||||
|
||||
boolean success = authService.updateUserPassword(info.username, request.getNew_password());
|
||||
@ -212,7 +212,7 @@ public class AuthController {
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 检查是否使用默认密码
|
||||
* 对应 Python: /api/check-default-password
|
||||
@ -221,13 +221,13 @@ public class AuthController {
|
||||
public Map<String, Boolean> checkDefaultPassword(HttpServletRequest httpRequest) {
|
||||
String token = getTokenFromRequest(httpRequest);
|
||||
TokenService.TokenInfo info = tokenService.verifyToken(token);
|
||||
|
||||
|
||||
Map<String, Boolean> response = new HashMap<>();
|
||||
if (info == null || !info.isAdmin) {
|
||||
response.put("using_default", false);
|
||||
return response;
|
||||
response.put("using_default", false);
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
User adminUser = authService.verifyUserPassword(ADMIN_USERNAME, DEFAULT_ADMIN_PASSWORD);
|
||||
response.put("using_default", adminUser != null);
|
||||
return response;
|
||||
@ -272,7 +272,7 @@ public class AuthController {
|
||||
this.is_admin = isAdmin;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Data
|
||||
public static class ChangePasswordRequest {
|
||||
private String current_password;
|
||||
|
||||
@ -0,0 +1,34 @@
|
||||
package com.xianyu.autoreply.controller;
|
||||
|
||||
import com.xianyu.autoreply.service.TokenService;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 用于处理 Admin 账号的特殊逻辑
|
||||
*
|
||||
* @author wangli
|
||||
* @since 2026-01-18 23:21
|
||||
*/
|
||||
@Data
|
||||
public abstract class BaseController {
|
||||
protected final TokenService tokenService;
|
||||
|
||||
protected boolean isAdmin(String token) {
|
||||
return isAdmin(getUserId(token));
|
||||
}
|
||||
|
||||
protected boolean isAdmin(Long userId) {
|
||||
return Objects.equals(1L, userId);
|
||||
}
|
||||
|
||||
// Helper to get user ID
|
||||
protected Long getUserId(String token) {
|
||||
if (token == null) throw new RuntimeException("Unauthorized");
|
||||
String rawToken = token.replace("Bearer ", "");
|
||||
TokenService.TokenInfo info = tokenService.verifyToken(rawToken);
|
||||
if (info == null) throw new RuntimeException("Unauthorized");
|
||||
return info.userId;
|
||||
}
|
||||
}
|
||||
@ -2,6 +2,7 @@ package com.xianyu.autoreply.controller;
|
||||
|
||||
import com.xianyu.autoreply.entity.CaptchaCode;
|
||||
import com.xianyu.autoreply.repository.CaptchaCodeRepository;
|
||||
import com.xianyu.autoreply.service.TokenService;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@ -15,12 +16,14 @@ import java.util.Map;
|
||||
@Slf4j
|
||||
@RestController
|
||||
// 注意:移除了类级别的 @RequestMapping("/api/captcha"),改用方法级别的根路径映射
|
||||
public class CaptchaController {
|
||||
public class CaptchaController extends BaseController {
|
||||
|
||||
private final CaptchaCodeRepository captchaCodeRepository;
|
||||
|
||||
@Autowired
|
||||
public CaptchaController(CaptchaCodeRepository captchaCodeRepository) {
|
||||
public CaptchaController(CaptchaCodeRepository captchaCodeRepository,
|
||||
TokenService tokenService) {
|
||||
super(tokenService);
|
||||
this.captchaCodeRepository = captchaCodeRepository;
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package com.xianyu.autoreply.controller;
|
||||
|
||||
import com.xianyu.autoreply.service.CaptchaSessionService;
|
||||
import com.xianyu.autoreply.service.TokenService;
|
||||
import lombok.Data;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
@ -13,12 +14,14 @@ import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/captcha")
|
||||
public class CaptchaRemoteController {
|
||||
public class CaptchaRemoteController extends BaseController {
|
||||
|
||||
private final CaptchaSessionService sessionService;
|
||||
|
||||
@Autowired
|
||||
public CaptchaRemoteController(CaptchaSessionService sessionService) {
|
||||
public CaptchaRemoteController(CaptchaSessionService sessionService,
|
||||
TokenService tokenService) {
|
||||
super(tokenService);
|
||||
this.sessionService = sessionService;
|
||||
}
|
||||
|
||||
@ -30,10 +33,10 @@ public class CaptchaRemoteController {
|
||||
map.put("session_id", id);
|
||||
map.put("completed", session.isCompleted());
|
||||
// has_websocket check would require exposing wsConnections from Handler, skipped for now
|
||||
map.put("has_websocket", true);
|
||||
map.put("has_websocket", true);
|
||||
sessions.add(map);
|
||||
});
|
||||
|
||||
|
||||
return Map.of("count", sessions.size(), "sessions", sessions);
|
||||
}
|
||||
|
||||
@ -41,7 +44,7 @@ public class CaptchaRemoteController {
|
||||
public Map<String, Object> getSessionInfo(@PathVariable String sessionId) {
|
||||
CaptchaSessionService.CaptchaSession session = sessionService.getSession(sessionId);
|
||||
if (session == null) throw new RuntimeException("会话不存在");
|
||||
|
||||
|
||||
Map<String, Object> resp = new HashMap<>();
|
||||
resp.put("session_id", sessionId);
|
||||
resp.put("screenshot", session.getScreenshot());
|
||||
@ -55,23 +58,23 @@ public class CaptchaRemoteController {
|
||||
public Map<String, String> getScreenshot(@PathVariable String sessionId) {
|
||||
CaptchaSessionService.CaptchaSession session = sessionService.getSession(sessionId);
|
||||
if (session == null) throw new RuntimeException("会话不存在");
|
||||
|
||||
|
||||
return Map.of("screenshot", session.getScreenshot());
|
||||
}
|
||||
|
||||
@PostMapping("/mouse_event")
|
||||
public Map<String, Object> handleMouseEvent(@RequestBody MouseEventRequest request) {
|
||||
boolean success = sessionService.handleMouseEvent(
|
||||
request.getSession_id(),
|
||||
request.getEvent_type(),
|
||||
request.getX(),
|
||||
request.getSession_id(),
|
||||
request.getEvent_type(),
|
||||
request.getX(),
|
||||
request.getY()
|
||||
);
|
||||
|
||||
|
||||
if (!success) throw new RuntimeException("处理失败");
|
||||
|
||||
|
||||
boolean completed = sessionService.checkCompletion(request.getSession_id());
|
||||
|
||||
|
||||
return Map.of("success", true, "completed", completed);
|
||||
}
|
||||
|
||||
@ -87,7 +90,7 @@ public class CaptchaRemoteController {
|
||||
sessionService.closeSession(sessionId);
|
||||
return Map.of("success", true);
|
||||
}
|
||||
|
||||
|
||||
// HTML Control Page serving could be done by Thymeleaf or Static Resource,
|
||||
// here returning simple string or checking static folder.
|
||||
// Python served specific HTML file.
|
||||
|
||||
@ -27,34 +27,27 @@ import java.util.stream.Collectors;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/cookies")
|
||||
public class CookieController {
|
||||
public class CookieController extends BaseController {
|
||||
|
||||
private final CookieRepository cookieRepository;
|
||||
private final XianyuClientService xianyuClientService;
|
||||
private final BrowserService browserService;
|
||||
private final TokenService tokenService;
|
||||
|
||||
@Autowired
|
||||
public CookieController(CookieRepository cookieRepository,
|
||||
XianyuClientService xianyuClientService,
|
||||
BrowserService browserService,
|
||||
TokenService tokenService) {
|
||||
super(tokenService);
|
||||
this.cookieRepository = cookieRepository;
|
||||
this.xianyuClientService = xianyuClientService;
|
||||
this.browserService = browserService;
|
||||
this.tokenService = tokenService;
|
||||
}
|
||||
|
||||
// Helper to get user ID
|
||||
private Long getUserId(String token) {
|
||||
if (token == null) throw new RuntimeException("Unauthorized");
|
||||
String rawToken = token.replace("Bearer ", "");
|
||||
TokenService.TokenInfo info = tokenService.verifyToken(rawToken);
|
||||
if (info == null) throw new RuntimeException("Unauthorized");
|
||||
return info.userId;
|
||||
}
|
||||
|
||||
|
||||
private void checkOwnership(Cookie cookie, Long userId) {
|
||||
if (isAdmin(userId)) return;
|
||||
if (cookie != null && !cookie.getUserId().equals(userId)) {
|
||||
throw new RuntimeException("Forbidden: You do not own this cookie");
|
||||
}
|
||||
@ -63,6 +56,9 @@ public class CookieController {
|
||||
@GetMapping
|
||||
public List<Cookie> listCookies(@RequestHeader(value = "Authorization", required = false) String token) {
|
||||
Long userId = getUserId(token);
|
||||
if (isAdmin(userId)) {
|
||||
return cookieRepository.findAll();
|
||||
}
|
||||
// Repository needs a method findByUserId. Assuming it exists.
|
||||
return cookieRepository.findByUserId(userId);
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package com.xianyu.autoreply.controller;
|
||||
|
||||
import cn.hutool.json.JSONObject;
|
||||
import com.xianyu.autoreply.service.TokenService;
|
||||
import com.xianyu.autoreply.utils.GeetestLib;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@ -13,12 +14,14 @@ import java.util.Map;
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/geetest")
|
||||
public class GeetestController {
|
||||
public class GeetestController extends BaseController {
|
||||
|
||||
private final GeetestLib geetestLib;
|
||||
|
||||
@Autowired
|
||||
public GeetestController(GeetestLib geetestLib) {
|
||||
public GeetestController(GeetestLib geetestLib,
|
||||
TokenService tokenService) {
|
||||
super(tokenService);
|
||||
this.geetestLib = geetestLib;
|
||||
}
|
||||
|
||||
@ -30,25 +33,25 @@ public class GeetestController {
|
||||
// 必传参数
|
||||
// digestmod: 加密算法,"md5", "sha256", "hmac-sha256"
|
||||
GeetestLib.GeetestResult result = geetestLib.register(GeetestLib.DigestMod.MD5, null, null);
|
||||
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
if (result.getStatus() == 1 || (result.getData() != null && result.getData().contains("\"success\": 0"))) {
|
||||
// status 1 means full success
|
||||
// or if it fallback mode (status might be 0 in Lib but we treat as success HTTP response with offline data)
|
||||
// Check GeetestLib: logic. It sets status=0 if logic fails?
|
||||
// GeetestLib: "初始化接口失败,后续流程走宕机模式" sets status=0.
|
||||
// But for the frontend, getting the offline parameters IS a successful API call.
|
||||
|
||||
response.put("success", true);
|
||||
response.put("code", 200);
|
||||
response.put("message", "获取成功");
|
||||
response.put("data", result.toJsonObject());
|
||||
// status 1 means full success
|
||||
// or if it fallback mode (status might be 0 in Lib but we treat as success HTTP response with offline data)
|
||||
// Check GeetestLib: logic. It sets status=0 if logic fails?
|
||||
// GeetestLib: "初始化接口失败,后续流程走宕机模式" sets status=0.
|
||||
// But for the frontend, getting the offline parameters IS a successful API call.
|
||||
|
||||
response.put("success", true);
|
||||
response.put("code", 200);
|
||||
response.put("message", "获取成功");
|
||||
response.put("data", result.toJsonObject());
|
||||
} else {
|
||||
response.put("success", false);
|
||||
response.put("code", 500);
|
||||
response.put("message", "获取验证参数失败: " + result.getMsg());
|
||||
response.put("success", false);
|
||||
response.put("code", 500);
|
||||
response.put("message", "获取验证参数失败: " + result.getMsg());
|
||||
}
|
||||
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
@ -58,20 +61,20 @@ public class GeetestController {
|
||||
@PostMapping("/validate")
|
||||
public Map<String, Object> validate(@RequestBody ValidateRequest request) {
|
||||
GeetestLib.GeetestResult result;
|
||||
|
||||
|
||||
// 这里的逻辑需要根据 register 返回的 new_captcha (gt_server_status) 来判断走 normal 还是 fail 模式
|
||||
// 但是在 Python SDK 的使用中,这个状态通常维护在 Session 中
|
||||
// 简单实现:如果不判断状态,默认尝试走 successValidate (正常模式)
|
||||
// 也可以让前端传回来,或者像 Python demo 那样存 session
|
||||
|
||||
|
||||
// 在 Python 的 reply_server.py 中,其实并没有展示完整的 validate 逻辑,
|
||||
// 这里我们按照 Standard Flow 实现
|
||||
|
||||
|
||||
result = geetestLib.successValidate(
|
||||
request.getChallenge(),
|
||||
request.getValidate(),
|
||||
request.getSeccode(),
|
||||
null,
|
||||
request.getChallenge(),
|
||||
request.getValidate(),
|
||||
request.getSeccode(),
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
@ -85,7 +88,7 @@ public class GeetestController {
|
||||
response.put("code", 400);
|
||||
response.put("message", "验证失败: " + result.getMsg());
|
||||
}
|
||||
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
@ -3,6 +3,8 @@ package com.xianyu.autoreply.controller;
|
||||
import com.xianyu.autoreply.entity.ItemInfo;
|
||||
import com.xianyu.autoreply.repository.CookieRepository;
|
||||
import com.xianyu.autoreply.repository.ItemInfoRepository;
|
||||
import com.xianyu.autoreply.service.TokenService;
|
||||
import com.xianyu.autoreply.service.XianyuClient;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@ -20,13 +22,16 @@ import java.util.stream.Collectors;
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping
|
||||
public class ItemController {
|
||||
public class ItemController extends BaseController {
|
||||
|
||||
private final ItemInfoRepository itemInfoRepository;
|
||||
private final CookieRepository cookieRepository;
|
||||
|
||||
@Autowired
|
||||
public ItemController(ItemInfoRepository itemInfoRepository, CookieRepository cookieRepository) {
|
||||
public ItemController(ItemInfoRepository itemInfoRepository,
|
||||
CookieRepository cookieRepository,
|
||||
TokenService tokenService) {
|
||||
super(tokenService);
|
||||
this.itemInfoRepository = itemInfoRepository;
|
||||
this.cookieRepository = cookieRepository;
|
||||
}
|
||||
@ -35,19 +40,20 @@ public class ItemController {
|
||||
|
||||
// GET /items - Get all items for current user (Aggregated)
|
||||
@GetMapping("/items")
|
||||
public Map<String, Object> getAllItems() {
|
||||
public Map<String, Object> getAllItems(@RequestHeader(value = "Authorization") String token) {
|
||||
// Migration assumption: Single user or Admin view, so we fetch all cookies first.
|
||||
List<String> cookieIds = cookieRepository.findAll().stream()
|
||||
.map(com.xianyu.autoreply.entity.Cookie::getId)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
|
||||
List<ItemInfo> allItems = new ArrayList<>();
|
||||
if (!cookieIds.isEmpty()) {
|
||||
for (String cid : cookieIds) {
|
||||
allItems.addAll(itemInfoRepository.findByCookieId(cid));
|
||||
}
|
||||
for (String cid : cookieIds) {
|
||||
allItems.addAll(itemInfoRepository.findByCookieId(cid));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return Map.of("items", allItems);
|
||||
}
|
||||
|
||||
@ -55,7 +61,7 @@ public class ItemController {
|
||||
public List<ItemInfo> getItems(@PathVariable String cid) {
|
||||
return itemInfoRepository.findByCookieId(cid);
|
||||
}
|
||||
|
||||
|
||||
// Alias for consistency
|
||||
@GetMapping("/items/cookie/{cookie_id}")
|
||||
public List<ItemInfo> getItemsAlias(@PathVariable String cookie_id) {
|
||||
@ -69,22 +75,23 @@ public class ItemController {
|
||||
}
|
||||
|
||||
@PutMapping("/items/{cookie_id}/{item_id}")
|
||||
public Map<String, Object> updateItem(@PathVariable String cookie_id,
|
||||
@PathVariable String item_id,
|
||||
public Map<String, Object> updateItem(@PathVariable String cookie_id,
|
||||
@PathVariable String item_id,
|
||||
@RequestBody ItemInfo itemUpdate) {
|
||||
ItemInfo item = itemInfoRepository.findByCookieIdAndItemId(cookie_id, item_id)
|
||||
.orElseThrow(() -> new RuntimeException("Item not found"));
|
||||
|
||||
|
||||
if (itemUpdate.getItemTitle() != null) item.setItemTitle(itemUpdate.getItemTitle());
|
||||
if (itemUpdate.getItemDescription() != null) item.setItemDescription(itemUpdate.getItemDescription());
|
||||
if (itemUpdate.getItemPrice() != null) item.setItemPrice(itemUpdate.getItemPrice());
|
||||
if (itemUpdate.getItemDetail() != null) item.setItemDetail(itemUpdate.getItemDetail());
|
||||
if (itemUpdate.getItemCategory() != null) item.setItemCategory(itemUpdate.getItemCategory());
|
||||
|
||||
|
||||
// Specific flags
|
||||
if (itemUpdate.getIsMultiSpec() != null) item.setIsMultiSpec(itemUpdate.getIsMultiSpec());
|
||||
if (itemUpdate.getMultiQuantityDelivery() != null) item.setMultiQuantityDelivery(itemUpdate.getMultiQuantityDelivery());
|
||||
|
||||
if (itemUpdate.getMultiQuantityDelivery() != null)
|
||||
item.setMultiQuantityDelivery(itemUpdate.getMultiQuantityDelivery());
|
||||
|
||||
itemInfoRepository.save(item);
|
||||
return Map.of("success", true, "msg", "Item updated", "data", item);
|
||||
}
|
||||
@ -92,8 +99,8 @@ public class ItemController {
|
||||
@Transactional
|
||||
@DeleteMapping("/items/{cookie_id}/{item_id}")
|
||||
public Map<String, Object> deleteItem(@PathVariable String cookie_id, @PathVariable String item_id) {
|
||||
itemInfoRepository.deleteByCookieIdAndItemId(cookie_id, item_id);
|
||||
return Map.of("success", true, "msg", "Item deleted");
|
||||
itemInfoRepository.deleteByCookieIdAndItemId(cookie_id, item_id);
|
||||
return Map.of("success", true, "msg", "Item deleted");
|
||||
}
|
||||
|
||||
// ------------------------- Batch Operations -------------------------
|
||||
@ -122,13 +129,13 @@ public class ItemController {
|
||||
@PostMapping("/items/search_multiple")
|
||||
public Map<String, Object> searchItemsMultiple(@RequestBody MultiSearchRequest request) {
|
||||
if (request.getCookie_ids() == null || request.getCookie_ids().isEmpty()) {
|
||||
return Map.of("success", false, "message", "No cookie IDs provided");
|
||||
return Map.of("success", false, "message", "No cookie IDs provided");
|
||||
}
|
||||
|
||||
|
||||
String keyword = request.getKeyword() != null ? request.getKeyword() : "";
|
||||
List<ItemInfo> items = itemInfoRepository.findByCookieIdInAndItemTitleContainingIgnoreCase(
|
||||
request.getCookie_ids(), keyword);
|
||||
|
||||
|
||||
return Map.of("success", true, "data", items);
|
||||
}
|
||||
|
||||
@ -139,23 +146,23 @@ public class ItemController {
|
||||
try {
|
||||
int page = request.getPage_number() > 0 ? request.getPage_number() - 1 : 0;
|
||||
int size = request.getPage_size() > 0 ? request.getPage_size() : 20;
|
||||
|
||||
|
||||
Pageable pageable = PageRequest.of(page, size, Sort.by("updatedAt").descending());
|
||||
Page<ItemInfo> pageResult;
|
||||
|
||||
|
||||
if (request.getKeyword() != null && !request.getKeyword().isEmpty()) {
|
||||
pageResult = itemInfoRepository.findByCookieIdAndItemTitleContainingIgnoreCase(
|
||||
request.getCookie_id(), request.getKeyword(), pageable);
|
||||
} else {
|
||||
pageResult = itemInfoRepository.findByCookieId(request.getCookie_id(), pageable);
|
||||
}
|
||||
|
||||
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("items", pageResult.getContent());
|
||||
data.put("total", pageResult.getTotalElements());
|
||||
data.put("current_page", request.getPage_number());
|
||||
data.put("total_pages", pageResult.getTotalPages());
|
||||
|
||||
|
||||
return Map.of("success", true, "data", data);
|
||||
} catch (Exception e) {
|
||||
log.error("Error getting items by page", e);
|
||||
@ -166,12 +173,12 @@ public class ItemController {
|
||||
// ------------------------- Specific Feature Updates -------------------------
|
||||
|
||||
@PutMapping("/items/{cookie_id}/{item_id}/multi-spec")
|
||||
public Map<String, Object> updateMultiSpec(@PathVariable String cookie_id,
|
||||
@PathVariable String item_id,
|
||||
@RequestBody Map<String, Boolean> body) {
|
||||
public Map<String, Object> updateMultiSpec(@PathVariable String cookie_id,
|
||||
@PathVariable String item_id,
|
||||
@RequestBody Map<String, Boolean> body) {
|
||||
ItemInfo item = itemInfoRepository.findByCookieIdAndItemId(cookie_id, item_id)
|
||||
.orElseThrow(() -> new RuntimeException("Item not found"));
|
||||
|
||||
|
||||
Boolean enabled = body.get("enabled");
|
||||
if (enabled != null) {
|
||||
item.setIsMultiSpec(enabled);
|
||||
@ -181,12 +188,12 @@ public class ItemController {
|
||||
}
|
||||
|
||||
@PutMapping("/items/{cookie_id}/{item_id}/multi-quantity-delivery")
|
||||
public Map<String, Object> updateMultiQuantityDelivery(@PathVariable String cookie_id,
|
||||
@PathVariable String item_id,
|
||||
@RequestBody Map<String, Boolean> body) {
|
||||
public Map<String, Object> updateMultiQuantityDelivery(@PathVariable String cookie_id,
|
||||
@PathVariable String item_id,
|
||||
@RequestBody Map<String, Boolean> body) {
|
||||
ItemInfo item = itemInfoRepository.findByCookieIdAndItemId(cookie_id, item_id)
|
||||
.orElseThrow(() -> new RuntimeException("Item not found"));
|
||||
|
||||
|
||||
Boolean enabled = body.get("enabled");
|
||||
if (enabled != null) {
|
||||
item.setMultiQuantityDelivery(enabled);
|
||||
@ -197,18 +204,52 @@ public class ItemController {
|
||||
|
||||
// ------------------------- Sync (Stub/Trigger) -------------------------
|
||||
|
||||
/**
|
||||
* 从账号获取所有商品(真实实现)
|
||||
* 对应Python: @app.post("/items/get-all-from-account")
|
||||
*/
|
||||
@PostMapping("/items/get-all-from-account")
|
||||
public Map<String, Object> getAllFromAccount(@RequestBody Map<String, String> body) {
|
||||
// In Python this triggers a background crawler task.
|
||||
// We will log this action and return success.
|
||||
// Real implementation requires bridging to the crawler service (Python/Node/Java).
|
||||
String cookieId = body.get("cookie_id");
|
||||
log.info("Triggering item sync for cookie: {}", cookieId);
|
||||
// Logic to clear existing items logic if needed or it's an upsert process
|
||||
// For now, assuming external crawler pushes data to DB
|
||||
return Map.of("success", true, "message", "Sync task started (Backend Received)");
|
||||
if (cookieId == null || cookieId.isEmpty()) {
|
||||
return Map.of("success", false, "message", "缺少cookie_id参数");
|
||||
}
|
||||
|
||||
log.info("触发商品同步任务,cookieId: {}", cookieId);
|
||||
|
||||
try {
|
||||
// 从全局实例字典获取XianyuClient实例
|
||||
XianyuClient client = XianyuClient.getInstance(cookieId);
|
||||
if (client == null) {
|
||||
return Map.of("success", false, "message", "未找到该账号的活跃连接,请确保账号已启用");
|
||||
}
|
||||
|
||||
// 调用getAllItems方法获取所有商品
|
||||
Map<String, Object> result = client.getAllItems(20, null);
|
||||
|
||||
if (Boolean.TRUE.equals(result.get("success"))) {
|
||||
int totalCount = (int) result.get("total_count");
|
||||
int totalPages = (int) result.get("total_pages");
|
||||
int savedCount = (int) result.get("total_saved");
|
||||
|
||||
return Map.of(
|
||||
"success", true,
|
||||
"message", String.format("成功获取商品,共 %d 件,保存 %d 件", totalCount, savedCount),
|
||||
"total_count", totalCount,
|
||||
"total_pages", totalPages,
|
||||
"saved_count", savedCount
|
||||
);
|
||||
} else {
|
||||
String error = (String) result.getOrDefault("error", "未知错误");
|
||||
return Map.of("success", false, "message", "获取商品失败: " + error);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("获取账号商品信息异常: {}", e.getMessage(), e);
|
||||
return Map.of("success", false, "message", "获取商品信息异常: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ------------------------- DTOs -------------------------
|
||||
|
||||
@Data
|
||||
|
||||
@ -35,7 +35,7 @@ import java.util.stream.Collectors;
|
||||
|
||||
@RestController
|
||||
@RequestMapping
|
||||
public class KeywordController {
|
||||
public class KeywordController extends BaseController {
|
||||
|
||||
private final KeywordRepository keywordRepository;
|
||||
private final DefaultReplyRepository defaultReplyRepository;
|
||||
@ -43,7 +43,6 @@ public class KeywordController {
|
||||
private final AiReplySettingRepository aiReplySettingRepository;
|
||||
private final CookieRepository cookieRepository;
|
||||
private final AiReplyService aiReplyService;
|
||||
private final TokenService tokenService;
|
||||
|
||||
@Autowired
|
||||
public KeywordController(KeywordRepository keywordRepository,
|
||||
@ -53,13 +52,13 @@ public class KeywordController {
|
||||
CookieRepository cookieRepository,
|
||||
AiReplyService aiReplyService,
|
||||
TokenService tokenService) {
|
||||
super(tokenService);
|
||||
this.keywordRepository = keywordRepository;
|
||||
this.defaultReplyRepository = defaultReplyRepository;
|
||||
this.defaultReplyRecordRepository = defaultReplyRecordRepository;
|
||||
this.aiReplySettingRepository = aiReplySettingRepository;
|
||||
this.cookieRepository = cookieRepository;
|
||||
this.aiReplyService = aiReplyService;
|
||||
this.tokenService = tokenService;
|
||||
}
|
||||
|
||||
// ------------------------- Keywords -------------------------
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
package com.xianyu.autoreply.controller;
|
||||
|
||||
import com.xianyu.autoreply.repository.SystemSettingRepository;
|
||||
import com.xianyu.autoreply.service.TokenService;
|
||||
import com.xianyu.autoreply.service.XianyuClient;
|
||||
import com.xianyu.autoreply.service.XianyuClientService;
|
||||
import lombok.Data;
|
||||
@ -11,17 +13,19 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/send-message")
|
||||
public class MessageController {
|
||||
public class MessageController extends BaseController {
|
||||
|
||||
private final XianyuClientService xianyuClientService;
|
||||
private final com.xianyu.autoreply.repository.SystemSettingRepository systemSettingRepository;
|
||||
private final SystemSettingRepository systemSettingRepository;
|
||||
|
||||
// Default key matching Python's API_SECRET_KEY
|
||||
private static final String DEFAULT_API_KEY = "xianyu_api_secret_2024";
|
||||
|
||||
@Autowired
|
||||
public MessageController(XianyuClientService xianyuClientService,
|
||||
com.xianyu.autoreply.repository.SystemSettingRepository systemSettingRepository) {
|
||||
SystemSettingRepository systemSettingRepository,
|
||||
TokenService tokenService) {
|
||||
super(tokenService);
|
||||
this.xianyuClientService = xianyuClientService;
|
||||
this.systemSettingRepository = systemSettingRepository;
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@ import com.xianyu.autoreply.entity.Cookie;
|
||||
import com.xianyu.autoreply.repository.CookieRepository;
|
||||
import com.xianyu.autoreply.repository.MessageNotificationRepository;
|
||||
import com.xianyu.autoreply.repository.NotificationChannelRepository;
|
||||
import com.xianyu.autoreply.service.TokenService;
|
||||
import lombok.Data;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@ -22,7 +23,7 @@ import java.util.stream.Collectors;
|
||||
|
||||
@RestController
|
||||
@RequestMapping
|
||||
public class NotificationController {
|
||||
public class NotificationController extends BaseController {
|
||||
|
||||
private final NotificationChannelRepository channelRepository;
|
||||
private final MessageNotificationRepository notificationRepository;
|
||||
@ -31,7 +32,9 @@ public class NotificationController {
|
||||
@Autowired
|
||||
public NotificationController(NotificationChannelRepository channelRepository,
|
||||
MessageNotificationRepository notificationRepository,
|
||||
CookieRepository cookieRepository) {
|
||||
CookieRepository cookieRepository,
|
||||
TokenService tokenService) {
|
||||
super(tokenService);
|
||||
this.channelRepository = channelRepository;
|
||||
this.notificationRepository = notificationRepository;
|
||||
this.cookieRepository = cookieRepository;
|
||||
|
||||
@ -4,6 +4,7 @@ import com.xianyu.autoreply.entity.Cookie;
|
||||
import com.xianyu.autoreply.entity.Order;
|
||||
import com.xianyu.autoreply.repository.CookieRepository;
|
||||
import com.xianyu.autoreply.repository.OrderRepository;
|
||||
import com.xianyu.autoreply.service.TokenService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
@ -16,13 +17,16 @@ import java.util.stream.Collectors;
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/orders")
|
||||
public class OrderController {
|
||||
public class OrderController extends BaseController {
|
||||
|
||||
private final OrderRepository orderRepository;
|
||||
private final CookieRepository cookieRepository;
|
||||
|
||||
@Autowired
|
||||
public OrderController(OrderRepository orderRepository, CookieRepository cookieRepository) {
|
||||
public OrderController(OrderRepository orderRepository,
|
||||
CookieRepository cookieRepository,
|
||||
TokenService tokenService) {
|
||||
super(tokenService);
|
||||
this.orderRepository = orderRepository;
|
||||
this.cookieRepository = cookieRepository;
|
||||
}
|
||||
@ -33,11 +37,11 @@ public class OrderController {
|
||||
// For simple migration assuming "admin" or checking cookies.
|
||||
// Python logic iterates user cookies and fetches orders.
|
||||
// Here we mock "current user" context by fetching all cookies (User 1 assumption again)
|
||||
|
||||
|
||||
List<String> cookieIds = cookieRepository.findAll().stream()
|
||||
.map(Cookie::getId)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
|
||||
List<Order> result = new ArrayList<>();
|
||||
for (String cid : cookieIds) {
|
||||
result.addAll(orderRepository.findByCookieId(cid));
|
||||
@ -50,14 +54,14 @@ public class OrderController {
|
||||
// Python checks ownership. We will just check existence first.
|
||||
Order order = orderRepository.findById(orderId)
|
||||
.orElseThrow(() -> new RuntimeException("订单不存在"));
|
||||
|
||||
|
||||
return Map.of("success", true, "data", order);
|
||||
}
|
||||
|
||||
|
||||
@DeleteMapping("/{orderId}")
|
||||
public Map<String, Object> deleteOrder(@PathVariable String orderId) {
|
||||
if (!orderRepository.existsById(orderId)) {
|
||||
throw new RuntimeException("订单不存在");
|
||||
throw new RuntimeException("订单不存在");
|
||||
}
|
||||
orderRepository.deleteById(orderId);
|
||||
return Map.of("success", true, "message", "删除成功");
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package com.xianyu.autoreply.controller;
|
||||
|
||||
import com.xianyu.autoreply.service.BrowserService;
|
||||
import com.xianyu.autoreply.service.TokenService;
|
||||
import lombok.Data;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
@ -9,15 +10,15 @@ import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@RestController
|
||||
public class PasswordLoginController {
|
||||
public class PasswordLoginController extends BaseController {
|
||||
|
||||
private final BrowserService browserService;
|
||||
private final com.xianyu.autoreply.service.TokenService tokenService;
|
||||
|
||||
@Autowired
|
||||
public PasswordLoginController(BrowserService browserService, com.xianyu.autoreply.service.TokenService tokenService) {
|
||||
public PasswordLoginController(BrowserService browserService,
|
||||
TokenService tokenService) {
|
||||
super(tokenService);
|
||||
this.browserService = browserService;
|
||||
this.tokenService = tokenService;
|
||||
}
|
||||
|
||||
@PostMapping("/password-login")
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package com.xianyu.autoreply.controller;
|
||||
|
||||
import com.xianyu.autoreply.service.QrLoginService;
|
||||
import com.xianyu.autoreply.service.TokenService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
@ -10,12 +11,14 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
public class QrLoginController {
|
||||
public class QrLoginController extends BaseController {
|
||||
|
||||
private final QrLoginService qrLoginService;
|
||||
|
||||
@Autowired
|
||||
public QrLoginController(QrLoginService qrLoginService) {
|
||||
public QrLoginController(QrLoginService qrLoginService,
|
||||
TokenService tokenService) {
|
||||
super(tokenService);
|
||||
this.qrLoginService = qrLoginService;
|
||||
}
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@ package com.xianyu.autoreply.controller;
|
||||
|
||||
import com.xianyu.autoreply.entity.UserStats;
|
||||
import com.xianyu.autoreply.repository.UserStatsRepository;
|
||||
import com.xianyu.autoreply.service.TokenService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
@ -15,12 +16,14 @@ import java.util.stream.Collectors;
|
||||
@Slf4j
|
||||
@RestController
|
||||
// Removing class level @RequestMapping to support root paths /statistics
|
||||
public class StatsController {
|
||||
public class StatsController extends BaseController {
|
||||
|
||||
private final UserStatsRepository userStatsRepository;
|
||||
|
||||
@Autowired
|
||||
public StatsController(UserStatsRepository userStatsRepository) {
|
||||
public StatsController(UserStatsRepository userStatsRepository,
|
||||
TokenService tokenService) {
|
||||
super(tokenService);
|
||||
this.userStatsRepository = userStatsRepository;
|
||||
}
|
||||
|
||||
@ -28,20 +31,20 @@ public class StatsController {
|
||||
public Map<String, Object> receiveUserStats(@RequestBody UserStatsDto data) {
|
||||
try {
|
||||
if (data.anonymous_id == null) {
|
||||
return Map.of("status", "error", "message", "Missing anonymous_id");
|
||||
return Map.of("status", "error", "message", "Missing anonymous_id");
|
||||
}
|
||||
|
||||
|
||||
String os = "unknown";
|
||||
String version = "2.2.0";
|
||||
|
||||
|
||||
if (data.info != null) {
|
||||
os = (String) data.info.getOrDefault("os", "unknown");
|
||||
version = (String) data.info.getOrDefault("version", "2.2.0");
|
||||
}
|
||||
|
||||
|
||||
UserStats stats = userStatsRepository.findByAnonymousId(data.anonymous_id)
|
||||
.orElse(new UserStats());
|
||||
|
||||
|
||||
if (stats.getId() == null) {
|
||||
stats.setAnonymousId(data.anonymous_id);
|
||||
stats.setFirstSeen(LocalDateTime.now());
|
||||
@ -53,12 +56,12 @@ public class StatsController {
|
||||
stats.setOs(os);
|
||||
stats.setVersion(version);
|
||||
stats.setInfo(data.info);
|
||||
|
||||
|
||||
userStatsRepository.save(stats);
|
||||
|
||||
|
||||
log.info("Received user stats: {}", data.anonymous_id);
|
||||
return Map.of("status", "success", "message", "User stats received");
|
||||
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Error saving stats", e);
|
||||
return Map.of("status", "error", "message", "Error saving stats");
|
||||
@ -71,36 +74,36 @@ public class StatsController {
|
||||
long totalUsers = userStatsRepository.count();
|
||||
long dailyActive = userStatsRepository.countActiveUsersSince(LocalDateTime.now().minusDays(1));
|
||||
long weeklyActive = userStatsRepository.countActiveUsersSince(LocalDateTime.now().minusDays(7));
|
||||
|
||||
|
||||
List<UserStats> all = userStatsRepository.findAll();
|
||||
|
||||
|
||||
Map<String, Long> osDistribution = all.stream()
|
||||
.collect(Collectors.groupingBy(u -> u.getOs() == null ? "unknown" : u.getOs(), Collectors.counting()));
|
||||
|
||||
|
||||
Map<String, Long> versionDistribution = all.stream()
|
||||
.collect(Collectors.groupingBy(u -> u.getVersion() == null ? "unknown" : u.getVersion(), Collectors.counting()));
|
||||
|
||||
|
||||
return Map.of(
|
||||
"total_users", totalUsers,
|
||||
"daily_active_users", dailyActive,
|
||||
"weekly_active_users", weeklyActive,
|
||||
"os_distribution", osDistribution,
|
||||
"version_distribution", versionDistribution,
|
||||
"last_updated", LocalDateTime.now().toString()
|
||||
"total_users", totalUsers,
|
||||
"daily_active_users", dailyActive,
|
||||
"weekly_active_users", weeklyActive,
|
||||
"os_distribution", osDistribution,
|
||||
"version_distribution", versionDistribution,
|
||||
"last_updated", LocalDateTime.now().toString()
|
||||
);
|
||||
} catch (Exception e) {
|
||||
return Map.of("error", e.getMessage());
|
||||
return Map.of("error", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/stats/recent")
|
||||
public Map<String, Object> getRecentUsers() {
|
||||
List<UserStats> recent = userStatsRepository.findTop20ByOrderByLastSeenDesc();
|
||||
|
||||
|
||||
List<Map<String, Object>> mapped = recent.stream().map(u -> {
|
||||
String maskedId = u.getAnonymousId();
|
||||
if (maskedId.length() > 8) maskedId = maskedId.substring(0, 8) + "****";
|
||||
|
||||
|
||||
// Need to return specific keys
|
||||
Map<String, Object> m = new HashMap<>();
|
||||
m.put("anonymous_id", maskedId);
|
||||
@ -111,10 +114,10 @@ public class StatsController {
|
||||
m.put("total_reports", u.getTotalReports());
|
||||
return m;
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
|
||||
return Map.of("recent_users", mapped);
|
||||
}
|
||||
|
||||
|
||||
// DTO class
|
||||
public static class UserStatsDto {
|
||||
public String anonymous_id;
|
||||
|
||||
@ -2,6 +2,7 @@ package com.xianyu.autoreply.controller;
|
||||
|
||||
import com.xianyu.autoreply.entity.SystemSetting;
|
||||
import com.xianyu.autoreply.repository.SystemSettingRepository;
|
||||
import com.xianyu.autoreply.service.TokenService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
@ -16,12 +17,14 @@ import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/system")
|
||||
public class SystemController {
|
||||
public class SystemController extends BaseController {
|
||||
|
||||
private final SystemSettingRepository systemSettingRepository;
|
||||
|
||||
@Autowired
|
||||
public SystemController(SystemSettingRepository systemSettingRepository) {
|
||||
public SystemController(SystemSettingRepository systemSettingRepository,
|
||||
TokenService tokenService) {
|
||||
super(tokenService);
|
||||
this.systemSettingRepository = systemSettingRepository;
|
||||
}
|
||||
|
||||
@ -41,10 +44,10 @@ public class SystemController {
|
||||
// In real app, fetch from DB. For now, mock or fetch if safe.
|
||||
// Python code filters keys: registration_enabled, show_default_login_info, login_captcha_enabled
|
||||
// We can reuse getSettings() and filter.
|
||||
|
||||
|
||||
List<SystemSetting> all = systemSettingRepository.findAll();
|
||||
Map<String, String> publicSettings = new java.util.HashMap<>();
|
||||
|
||||
|
||||
// Defaults
|
||||
publicSettings.put("registration_enabled", "true");
|
||||
publicSettings.put("show_default_login_info", "true");
|
||||
@ -52,9 +55,9 @@ public class SystemController {
|
||||
|
||||
for (SystemSetting setting : all) {
|
||||
String key = setting.getKey();
|
||||
if ("registration_enabled".equals(key) ||
|
||||
"show_default_login_info".equals(key) ||
|
||||
"login_captcha_enabled".equals(key)) {
|
||||
if ("registration_enabled".equals(key) ||
|
||||
"show_default_login_info".equals(key) ||
|
||||
"login_captcha_enabled".equals(key)) {
|
||||
publicSettings.put(key, setting.getValue());
|
||||
}
|
||||
}
|
||||
@ -66,7 +69,7 @@ public class SystemController {
|
||||
public Map<String, Object> checkVersion() {
|
||||
// In Python this calls an external URL.
|
||||
// We return a dummy response or implement the HTTP call using Hutool if needed.
|
||||
return Collections.singletonMap("version", "1.0.0");
|
||||
return Collections.singletonMap("version", "1.0.0");
|
||||
}
|
||||
|
||||
@GetMapping("/version/changelog")
|
||||
|
||||
@ -2,6 +2,7 @@ package com.xianyu.autoreply.controller;
|
||||
|
||||
import com.xianyu.autoreply.entity.SystemSetting;
|
||||
import com.xianyu.autoreply.repository.SystemSettingRepository;
|
||||
import com.xianyu.autoreply.service.TokenService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
@ -19,12 +20,14 @@ import java.util.stream.Collectors;
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/system-settings")
|
||||
public class SystemSettingController {
|
||||
public class SystemSettingController extends BaseController {
|
||||
|
||||
private final SystemSettingRepository systemSettingRepository;
|
||||
|
||||
@Autowired
|
||||
public SystemSettingController(SystemSettingRepository systemSettingRepository) {
|
||||
public SystemSettingController(SystemSettingRepository systemSettingRepository,
|
||||
TokenService tokenService) {
|
||||
super(tokenService);
|
||||
this.systemSettingRepository = systemSettingRepository;
|
||||
}
|
||||
|
||||
|
||||
@ -6,9 +6,11 @@ import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONArray;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.xianyu.autoreply.entity.Cookie;
|
||||
import com.xianyu.autoreply.entity.ItemInfo;
|
||||
import com.xianyu.autoreply.model.ItemDetailCache;
|
||||
import com.xianyu.autoreply.model.LockHoldInfo;
|
||||
import com.xianyu.autoreply.repository.CookieRepository;
|
||||
import com.xianyu.autoreply.repository.ItemInfoRepository;
|
||||
import com.xianyu.autoreply.service.captcha.CaptchaHandler;
|
||||
import com.xianyu.autoreply.utils.XianyuUtils;
|
||||
import jakarta.websocket.ContainerProvider;
|
||||
@ -97,6 +99,7 @@ public class XianyuClient extends TextWebSocketHandler {
|
||||
private final BrowserService browserService;
|
||||
private final PauseManager pauseManager; // 暂停管理器
|
||||
private final OrderStatusHandler orderStatusHandler; // 订单状态处理器
|
||||
private final ItemInfoRepository itemInfoRepository; // 商品信息存储库
|
||||
|
||||
private String cookiesStr; // Cookie字符串
|
||||
private Map<String, String> cookies; // Cookie字典
|
||||
@ -217,7 +220,8 @@ public class XianyuClient extends TextWebSocketHandler {
|
||||
public XianyuClient(String cookieId, CookieRepository cookieRepository,
|
||||
ReplyService replyService, CaptchaHandler captchaHandler,
|
||||
BrowserService browserService, PauseManager pauseManager,
|
||||
OrderStatusHandler orderStatusHandler) {
|
||||
OrderStatusHandler orderStatusHandler,
|
||||
ItemInfoRepository itemInfoRepository) {
|
||||
this.cookieId = cookieId;
|
||||
this.cookieRepository = cookieRepository;
|
||||
this.replyService = replyService;
|
||||
@ -225,6 +229,7 @@ public class XianyuClient extends TextWebSocketHandler {
|
||||
this.browserService = browserService;
|
||||
this.pauseManager = pauseManager;
|
||||
this.orderStatusHandler = orderStatusHandler;
|
||||
this.itemInfoRepository = itemInfoRepository;
|
||||
|
||||
// 创建HTTP客户端
|
||||
this.httpClient = new OkHttpClient.Builder()
|
||||
@ -3496,9 +3501,326 @@ public class XianyuClient extends TextWebSocketHandler {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ============== 商品信息获取相关方法(对应Python XianyuLive.get_all_items系列方法)==============
|
||||
|
||||
/**
|
||||
* 获取所有商品信息(自动分页)
|
||||
* 对应Python: async def get_all_items(self, page_size=20, max_pages=None)
|
||||
*
|
||||
* @param pageSize 每页数量,默认20
|
||||
* @param maxPages 最大页数限制,null表示无限制
|
||||
* @return 包含所有商品信息的Map
|
||||
*/
|
||||
public Map<String, Object> getAllItems(int pageSize, Integer maxPages) {
|
||||
log.info("【{}】开始获取所有商品信息,每页{}条", cookieId, pageSize);
|
||||
|
||||
int pageNumber = 1;
|
||||
int totalSaved = 0;
|
||||
int totalCount = 0;
|
||||
|
||||
while (true) {
|
||||
if (maxPages != null && pageNumber > maxPages) {
|
||||
log.info("【{}】达到最大页数限制 {},停止获取", cookieId, maxPages);
|
||||
break;
|
||||
}
|
||||
|
||||
log.info("【{}】正在获取第 {} 页...", cookieId, pageNumber);
|
||||
Map<String, Object> result = getItemListInfo(pageNumber, pageSize, 0);
|
||||
|
||||
if (!Boolean.TRUE.equals(result.get("success"))) {
|
||||
log.error("【{}】获取第 {} 页失败: {}", cookieId, pageNumber, result.get("error"));
|
||||
return Map.of(
|
||||
"success", false,
|
||||
"error", result.getOrDefault("error", "获取商品失败"),
|
||||
"total_pages", pageNumber - 1,
|
||||
"total_count", totalCount,
|
||||
"total_saved", totalSaved
|
||||
);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
java.util.List<Map<String, Object>> currentItems = (java.util.List<Map<String, Object>>) result.get("items");
|
||||
if (currentItems == null || currentItems.isEmpty()) {
|
||||
log.info("【{}】第 {} 页没有数据,获取完成", cookieId, pageNumber);
|
||||
break;
|
||||
}
|
||||
|
||||
totalCount += currentItems.size();
|
||||
Integer savedCount = (Integer) result.get("saved_count");
|
||||
if (savedCount != null) {
|
||||
totalSaved += savedCount;
|
||||
}
|
||||
|
||||
log.info("【{}】第 {} 页获取到 {} 个商品", cookieId, pageNumber, currentItems.size());
|
||||
|
||||
// 如果当前页商品数量少于页面大小,说明已经是最后一页
|
||||
if (currentItems.size() < pageSize) {
|
||||
log.info("【{}】第 {} 页商品数量({})少于页面大小({}),获取完成",
|
||||
cookieId, pageNumber, currentItems.size(), pageSize);
|
||||
break;
|
||||
}
|
||||
|
||||
pageNumber++;
|
||||
|
||||
// 添加延迟避免请求过快
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
log.warn("【{}】获取商品时被中断", cookieId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
log.info("【{}】所有商品获取完成,共 {} 个商品,保存了 {} 个", cookieId, totalCount, totalSaved);
|
||||
|
||||
return Map.of(
|
||||
"success", true,
|
||||
"total_pages", pageNumber,
|
||||
"total_count", totalCount,
|
||||
"total_saved", totalSaved
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取商品列表信息(单页)
|
||||
* 对应Python: async def get_item_list_info(self, page_number=1, page_size=20, retry_count=0)
|
||||
*
|
||||
* @param pageNumber 页码,从1开始
|
||||
* @param pageSize 每页数量
|
||||
* @param retryCount 重试次数(内部使用)
|
||||
* @return 包含商品列表的Map
|
||||
*/
|
||||
private Map<String, Object> getItemListInfo(int pageNumber, int pageSize, int retryCount) {
|
||||
if (retryCount >= 4) {
|
||||
log.error("【{}】获取商品信息失败,重试次数过多", cookieId);
|
||||
return Map.of("error", "获取商品信息失败,重试次数过多");
|
||||
}
|
||||
|
||||
try {
|
||||
// 构建请求参数
|
||||
long timestamp = System.currentTimeMillis();
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("jsv", "2.7.2");
|
||||
params.put("appKey", API_APP_KEY);
|
||||
params.put("t", String.valueOf(timestamp));
|
||||
params.put("sign", "");
|
||||
params.put("v", "1.0");
|
||||
params.put("type", "originaljson");
|
||||
params.put("accountSite", "xianyu");
|
||||
params.put("dataType", "json");
|
||||
params.put("timeout", "20000");
|
||||
params.put("api", "mtop.idle.web.xyh.item.list");
|
||||
params.put("sessionOption", "AutoLoginOnly");
|
||||
params.put("spm_cnt", "a21ybx.im.0.0");
|
||||
params.put("spm_pre", "a21ybx.collection.menu.1.272b5141NafCNK");
|
||||
|
||||
// 构建数据
|
||||
Map<String, Object> dataMap = new HashMap<>();
|
||||
dataMap.put("needGroupInfo", false);
|
||||
dataMap.put("pageNumber", pageNumber);
|
||||
dataMap.put("pageSize", pageSize);
|
||||
dataMap.put("groupName", "在售");
|
||||
dataMap.put("groupId", "58877261");
|
||||
dataMap.put("defaultGroup", true);
|
||||
dataMap.put("userId", myId);
|
||||
|
||||
String dataVal = JSON.toJSONString(dataMap);
|
||||
|
||||
// 从cookie中获取token
|
||||
String mh5tk = cookies.get("_m_h5_tk");
|
||||
String token = "";
|
||||
if (mh5tk != null && mh5tk.contains("_")) {
|
||||
token = mh5tk.split("_")[0];
|
||||
}
|
||||
|
||||
log.warn("【{}】准备获取商品列表,token: {}", cookieId, token);
|
||||
|
||||
// 生成签名
|
||||
String sign = XianyuUtils.generateSign(String.valueOf(timestamp), token, dataVal);
|
||||
params.put("sign", sign);
|
||||
|
||||
// 发送HTTP请求
|
||||
String url = "https://h5api.m.goofish.com/h5/mtop.idle.web.xyh.item.list/1.0/";
|
||||
cn.hutool.http.HttpRequest request = HttpRequest.post(url)
|
||||
.form("data", dataVal)
|
||||
.cookie(cookiesStr);
|
||||
|
||||
// 添加所有params参数
|
||||
for (Map.Entry<String, String> entry : params.entrySet()) {
|
||||
request.form(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
String responseBody = request.execute().body();
|
||||
|
||||
JSONObject resJson = JSON.parseObject(responseBody);
|
||||
log.info("【{}】商品信息获取响应: {}", cookieId, resJson.toJSONString());
|
||||
|
||||
// 检查响应是否成功
|
||||
JSONArray retArray = resJson.getJSONArray("ret");
|
||||
if (retArray != null && !retArray.isEmpty() && "SUCCESS::调用成功".equals(retArray.getString(0))) {
|
||||
JSONObject itemsData = resJson.getJSONObject("data");
|
||||
JSONArray cardList = itemsData.getJSONArray("cardList");
|
||||
|
||||
// 解析商品信息
|
||||
java.util.List<Map<String, Object>> itemsList = new java.util.ArrayList<>();
|
||||
if (cardList != null) {
|
||||
for (int i = 0; i < cardList.size(); i++) {
|
||||
JSONObject card = cardList.getJSONObject(i);
|
||||
JSONObject cardData = card.getJSONObject("cardData");
|
||||
if (cardData != null) {
|
||||
Map<String, Object> itemInfo = new HashMap<>();
|
||||
itemInfo.put("id", cardData.getString("id"));
|
||||
itemInfo.put("title", cardData.getString("title"));
|
||||
|
||||
JSONObject priceInfo = cardData.getJSONObject("priceInfo");
|
||||
if (priceInfo != null) {
|
||||
itemInfo.put("price", priceInfo.getString("price"));
|
||||
String priceText = (priceInfo.getString("preText") != null ? priceInfo.getString("preText") : "") +
|
||||
(priceInfo.getString("price") != null ? priceInfo.getString("price") : "");
|
||||
itemInfo.put("price_text", priceText);
|
||||
} else {
|
||||
itemInfo.put("price", "");
|
||||
itemInfo.put("price_text", "");
|
||||
}
|
||||
|
||||
itemInfo.put("category_id", cardData.getString("categoryId"));
|
||||
itemInfo.put("auction_type", cardData.getString("auctionType"));
|
||||
itemInfo.put("item_status", cardData.getInteger("itemStatus"));
|
||||
itemInfo.put("detail_url", cardData.getString("detailUrl"));
|
||||
itemInfo.put("pic_info", cardData.getJSONObject("picInfo"));
|
||||
itemInfo.put("detail_params", cardData.getJSONObject("detailParams"));
|
||||
itemInfo.put("track_params", cardData.getJSONObject("trackParams"));
|
||||
itemInfo.put("item_label_data", cardData.getJSONObject("itemLabelDataVO"));
|
||||
itemInfo.put("card_type", card.getInteger("cardType"));
|
||||
|
||||
itemsList.add(itemInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.info("【{}】成功获取到 {} 个商品", cookieId, itemsList.size());
|
||||
|
||||
// 打印商品详细信息到控制台
|
||||
System.out.println("\n" + "=".repeat(80));
|
||||
System.out.println(String.format("📦 账号 %s 的商品列表 (第%d页,%d 个商品)", myId, pageNumber, itemsList.size()));
|
||||
System.out.println("=".repeat(80));
|
||||
|
||||
for (int i = 0; i < itemsList.size(); i++) {
|
||||
Map<String, Object> item = itemsList.get(i);
|
||||
System.out.println(String.format("\n🔸 商品 %d:", i + 1));
|
||||
System.out.println(String.format(" 商品ID: %s", item.get("id")));
|
||||
System.out.println(String.format(" 商品标题: %s", item.get("title")));
|
||||
System.out.println(String.format(" 价格: %s", item.get("price_text")));
|
||||
System.out.println(String.format(" 分类ID: %s", item.get("category_id")));
|
||||
System.out.println(String.format(" 商品状态: %s", item.get("item_status")));
|
||||
System.out.println(String.format(" 拍卖类型: %s", item.get("auction_type")));
|
||||
System.out.println(String.format(" 详情链接: %s", item.get("detail_url")));
|
||||
}
|
||||
|
||||
System.out.println("\n" + "=".repeat(80));
|
||||
System.out.println("✅ 商品列表获取完成");
|
||||
System.out.println("=".repeat(80));
|
||||
|
||||
// 自动保存商品信息到数据库
|
||||
int savedCount = 0;
|
||||
if (!itemsList.isEmpty()) {
|
||||
savedCount = saveItemsToDatabase(itemsList);
|
||||
log.info("【{}】已将 {} 个商品信息保存到数据库", cookieId, savedCount);
|
||||
}
|
||||
|
||||
return Map.of(
|
||||
"success", true,
|
||||
"page_number", pageNumber,
|
||||
"page_size", pageSize,
|
||||
"current_count", itemsList.size(),
|
||||
"items", itemsList,
|
||||
"saved_count", savedCount
|
||||
);
|
||||
} else {
|
||||
// 检查是否是token失效
|
||||
String errorMsg = retArray != null && !retArray.isEmpty() ? retArray.getString(0) : "";
|
||||
if (errorMsg.contains("FAIL_SYS_TOKEN_EXOIRED") || errorMsg.toLowerCase().contains("token")) {
|
||||
log.warn("【{}】Token失效,准备重试: {}", cookieId, errorMsg);
|
||||
try {
|
||||
Thread.sleep(500);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
return getItemListInfo(pageNumber, pageSize, retryCount + 1);
|
||||
} else {
|
||||
log.error("【{}】获取商品信息失败: {}", cookieId, resJson.toJSONString());
|
||||
return Map.of("error", "获取商品信息失败: " + errorMsg);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("【{}】商品信息API请求异常", cookieId, e);
|
||||
try {
|
||||
Thread.sleep(500);
|
||||
} catch (InterruptedException ie) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
return getItemListInfo(pageNumber, pageSize,retryCount + 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存商品列表到数据库
|
||||
* 对应Python: async def save_items_list_to_db(self, items_list)
|
||||
*
|
||||
* @param itemsList 商品列表
|
||||
* @return 保存的商品数量
|
||||
*/
|
||||
private int saveItemsToDatabase(java.util.List<Map<String, Object>> itemsList) {
|
||||
int savedCount = 0;
|
||||
|
||||
try {
|
||||
for (Map<String, Object> itemData : itemsList) {
|
||||
try {
|
||||
String itemId = (String) itemData.get("id");
|
||||
if (itemId == null || itemId.isEmpty()) {
|
||||
log.warn("【{}】跳过保存:商品ID为空", cookieId);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 查找或创建商品实体
|
||||
ItemInfo itemInfo = itemInfoRepository.findByCookieIdAndItemId(cookieId, itemId)
|
||||
.orElse(new ItemInfo());
|
||||
|
||||
// 设置字段
|
||||
itemInfo.setCookieId(cookieId);
|
||||
itemInfo.setItemId(itemId);
|
||||
itemInfo.setItemTitle((String) itemData.get("title"));
|
||||
itemInfo.setItemPrice((String) itemData.get("price"));
|
||||
|
||||
// 尝试从 detail_params 中提取分类信息
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> detailParams = (Map<String, Object>) itemData.get("detail_params");
|
||||
if (detailParams != null) {
|
||||
Object categoryName = detailParams.get("categoryName");
|
||||
if (categoryName != null) {
|
||||
itemInfo.setItemCategory(categoryName.toString());
|
||||
}
|
||||
}
|
||||
|
||||
// 保存到数据库
|
||||
itemInfoRepository.save(itemInfo);
|
||||
savedCount++;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("【{}】保存商品信息失败: {}", cookieId, itemData.get("id"), e);
|
||||
}
|
||||
}
|
||||
|
||||
log.info("【{}】成功保存 {} 个商品到数据库", cookieId, savedCount);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("【{}】批量保存商品信息时出错", cookieId, e);
|
||||
}
|
||||
|
||||
return savedCount;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@ package com.xianyu.autoreply.service;
|
||||
|
||||
import com.xianyu.autoreply.entity.Cookie;
|
||||
import com.xianyu.autoreply.repository.CookieRepository;
|
||||
import com.xianyu.autoreply.repository.ItemInfoRepository;
|
||||
import com.xianyu.autoreply.service.captcha.CaptchaHandler;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@ -23,17 +24,19 @@ public class XianyuClientService {
|
||||
private final PauseManager pauseManager;
|
||||
private final OrderStatusHandler orderStatusHandler;
|
||||
private final Map<String, XianyuClient> clients = new ConcurrentHashMap<>();
|
||||
private final ItemInfoRepository itemInfoRepository;
|
||||
|
||||
@Autowired
|
||||
public XianyuClientService(CookieRepository cookieRepository, ReplyService replyService,
|
||||
public XianyuClientService(CookieRepository cookieRepository, ReplyService replyService,
|
||||
CaptchaHandler captchaHandler, BrowserService browserService,
|
||||
PauseManager pauseManager, OrderStatusHandler orderStatusHandler) {
|
||||
PauseManager pauseManager, OrderStatusHandler orderStatusHandler, ItemInfoRepository itemInfoRepository) {
|
||||
this.cookieRepository = cookieRepository;
|
||||
this.replyService = replyService;
|
||||
this.captchaHandler = captchaHandler;
|
||||
this.browserService = browserService;
|
||||
this.pauseManager = pauseManager;
|
||||
this.orderStatusHandler = orderStatusHandler;
|
||||
this.itemInfoRepository = itemInfoRepository;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
@ -52,8 +55,9 @@ public class XianyuClientService {
|
||||
log.warn("Client {} already running", cookieId);
|
||||
return;
|
||||
}
|
||||
XianyuClient client = new XianyuClient(cookieId, cookieRepository, replyService,
|
||||
captchaHandler, browserService, pauseManager, orderStatusHandler);
|
||||
XianyuClient client = new XianyuClient(cookieId, cookieRepository, replyService,
|
||||
captchaHandler, browserService, pauseManager, orderStatusHandler,
|
||||
itemInfoRepository);
|
||||
clients.put(cookieId, client);
|
||||
client.start();
|
||||
}
|
||||
@ -64,7 +68,7 @@ public class XianyuClientService {
|
||||
client.stop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public XianyuClient getClient(String cookieId) {
|
||||
return clients.get(cookieId);
|
||||
}
|
||||
|
||||
@ -1,17 +1,32 @@
|
||||
import { get, post, put, del } from '@/utils/request'
|
||||
import type { Account, AccountDetail, ApiResponse } from '@/types'
|
||||
|
||||
// 获取账号列表(返回账号ID数组)
|
||||
// 获取账号列表(返回账号对象数组)
|
||||
export const getAccounts = async (): Promise<Account[]> => {
|
||||
const ids: string[] = await get('/cookies')
|
||||
// 后端返回的是账号ID数组,转换为Account对象数组
|
||||
return ids.map(id => ({
|
||||
id,
|
||||
cookie: '',
|
||||
enabled: true,
|
||||
interface BackendAccount {
|
||||
id: string
|
||||
value: string
|
||||
remark?: string
|
||||
username?: string
|
||||
password?: string
|
||||
enabled: boolean
|
||||
created_at?: string
|
||||
updated_at?: string
|
||||
user_id?: number
|
||||
auto_confirm: boolean
|
||||
pause_duration?: number
|
||||
show_browser?: boolean
|
||||
}
|
||||
const data = await get<BackendAccount[]>('/cookies')
|
||||
// 后端返回的是完整账号对象数组,转换为前端Account格式
|
||||
return data.map(item => ({
|
||||
id: item.id,
|
||||
cookie: item.value || '',
|
||||
remark: item.remark,
|
||||
enabled: item.enabled,
|
||||
use_ai_reply: false,
|
||||
use_default_reply: false,
|
||||
auto_confirm: false
|
||||
auto_confirm: item.auto_confirm
|
||||
}))
|
||||
}
|
||||
|
||||
@ -33,6 +48,7 @@ export const getAccountDetails = async (): Promise<AccountDetail[]> => {
|
||||
return data.map((item) => ({
|
||||
id: item.id,
|
||||
cookie: item.value,
|
||||
remark: item.remark,
|
||||
enabled: item.enabled,
|
||||
auto_confirm: item.auto_confirm,
|
||||
note: item.remark,
|
||||
|
||||
@ -637,6 +637,7 @@ export function Accounts() {
|
||||
<thead>
|
||||
<tr>
|
||||
<th>账号ID</th>
|
||||
<th>备注</th>
|
||||
<th>关键词</th>
|
||||
<th>状态</th>
|
||||
<th>AI回复</th>
|
||||
@ -658,6 +659,7 @@ export function Accounts() {
|
||||
accounts.map((account) => (
|
||||
<tr key={account.id}>
|
||||
<td className="font-medium text-blue-600 dark:text-blue-400">{account.id}</td>
|
||||
<td className="font-medium text-blue-600 dark:text-blue-400">{account.remark}</td>
|
||||
<td>
|
||||
<span className="inline-flex items-center gap-1.5 text-sm">
|
||||
<MessageSquare className="w-3.5 h-3.5 text-blue-500" />
|
||||
|
||||
@ -26,6 +26,7 @@ export interface LoginResponse {
|
||||
export interface Account {
|
||||
id: string
|
||||
cookie: string
|
||||
remark?: string
|
||||
enabled: boolean
|
||||
use_ai_reply: boolean
|
||||
use_default_reply: boolean
|
||||
@ -109,14 +110,14 @@ export interface Order {
|
||||
updated_at?: string
|
||||
}
|
||||
|
||||
export type OrderStatus =
|
||||
| 'processing'
|
||||
export type OrderStatus =
|
||||
| 'processing'
|
||||
| 'pending_ship'
|
||||
| 'processed'
|
||||
| 'shipped'
|
||||
| 'completed'
|
||||
| 'processed'
|
||||
| 'shipped'
|
||||
| 'completed'
|
||||
| 'refunding'
|
||||
| 'cancelled'
|
||||
| 'cancelled'
|
||||
| 'unknown'
|
||||
|
||||
// 卡券相关类型
|
||||
|
||||
Loading…
Reference in New Issue
Block a user