Merge remote-tracking branch '群晖/master'
# Conflicts: # src/main/java/cn/van/business/util/JDUtil.java
This commit is contained in:
31
pom.xml
31
pom.xml
@@ -90,11 +90,6 @@
|
|||||||
<artifactId>jdk</artifactId>
|
<artifactId>jdk</artifactId>
|
||||||
<version>3.0</version>
|
<version>3.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>com.github.penggle</groupId>
|
|
||||||
<artifactId>kaptcha</artifactId>
|
|
||||||
<version>2.3.2</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.codehaus.jackson</groupId>
|
<groupId>org.codehaus.jackson</groupId>
|
||||||
<artifactId>jackson-mapper-asl</artifactId>
|
<artifactId>jackson-mapper-asl</artifactId>
|
||||||
@@ -106,29 +101,7 @@
|
|||||||
<artifactId>jackson-core-asl</artifactId>
|
<artifactId>jackson-core-asl</artifactId>
|
||||||
<version>1.9.2</version>
|
<version>1.9.2</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>io.jsonwebtoken</groupId>
|
|
||||||
<artifactId>jjwt-api</artifactId>
|
|
||||||
<version>0.11.5</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>io.jsonwebtoken</groupId>
|
|
||||||
<artifactId>jjwt-impl</artifactId>
|
|
||||||
<version>0.11.5</version>
|
|
||||||
<scope>runtime</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>io.jsonwebtoken</groupId>
|
|
||||||
<artifactId>jjwt-jackson</artifactId>
|
|
||||||
<version>0.11.5</version>
|
|
||||||
<scope>runtime</scope>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- Actuator -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<!-- AOP -->
|
<!-- AOP -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
@@ -140,10 +113,6 @@
|
|||||||
<artifactId>httpclient</artifactId>
|
<artifactId>httpclient</artifactId>
|
||||||
<version>4.5.13</version>
|
<version>4.5.13</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-security</artifactId>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
package cn.van.business.config;
|
|
||||||
|
|
||||||
import com.google.code.kaptcha.impl.DefaultKaptcha;
|
|
||||||
import com.google.code.kaptcha.util.Config;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
|
|
||||||
import java.util.Properties;
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
public class KaptchaConfig {
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public DefaultKaptcha kaptchaProducer() {
|
|
||||||
DefaultKaptcha kaptcha = new DefaultKaptcha();
|
|
||||||
Properties properties = new Properties();
|
|
||||||
|
|
||||||
// 设置验证码参数
|
|
||||||
properties.setProperty("kaptcha.image.width", "150");
|
|
||||||
properties.setProperty("kaptcha.image.height", "50");
|
|
||||||
properties.setProperty("kaptcha.textproducer.char.string", "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ");
|
|
||||||
properties.setProperty("kaptcha.textproducer.char.length", "4");
|
|
||||||
|
|
||||||
kaptcha.setConfig(new Config(properties));
|
|
||||||
return kaptcha;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
package cn.van.business.config;
|
|
||||||
|
|
||||||
import cn.van.business.filter.JwtAuthFilter;
|
|
||||||
import cn.van.business.service.impl.UserDetailsServiceImpl;
|
|
||||||
import cn.van.business.util.JwtUtils;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.security.authentication.AuthenticationManager;
|
|
||||||
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
|
|
||||||
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
|
||||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
|
||||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
|
||||||
import org.springframework.security.core.session.SessionRegistry;
|
|
||||||
import org.springframework.security.core.session.SessionRegistryImpl;
|
|
||||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
|
||||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
|
||||||
import org.springframework.security.web.session.HttpSessionEventPublisher;
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
@EnableWebSecurity
|
|
||||||
public class SecurityConfig {
|
|
||||||
|
|
||||||
private final UserDetailsServiceImpl userDetailsService;
|
|
||||||
private final JwtAuthFilter jwtAuthFilter;
|
|
||||||
|
|
||||||
public SecurityConfig(UserDetailsServiceImpl userDetailsService, JwtAuthFilter jwtAuthFilter) {
|
|
||||||
this.userDetailsService = userDetailsService;
|
|
||||||
this.jwtAuthFilter = jwtAuthFilter;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public SecurityFilterChain filterChain(HttpSecurity http,
|
|
||||||
PasswordEncoder passwordEncoder) throws Exception {
|
|
||||||
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
|
|
||||||
provider.setUserDetailsService(userDetailsService);
|
|
||||||
provider.setPasswordEncoder(passwordEncoder);
|
|
||||||
|
|
||||||
http
|
|
||||||
.csrf(AbstractHttpConfigurer::disable)
|
|
||||||
.sessionManagement(session -> session
|
|
||||||
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
|
||||||
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
|
|
||||||
.authenticationProvider(provider)
|
|
||||||
.authorizeHttpRequests(auth -> auth
|
|
||||||
.requestMatchers("/auth/login", "/auth/captcha","/wx/message").permitAll()
|
|
||||||
.anyRequest().authenticated());
|
|
||||||
|
|
||||||
return http.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public PasswordEncoder passwordEncoder() {
|
|
||||||
return new BCryptPasswordEncoder();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
|
|
||||||
return config.getAuthenticationManager();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public SessionRegistry sessionRegistry() {
|
|
||||||
return new SessionRegistryImpl();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public HttpSessionEventPublisher httpSessionEventPublisher() {
|
|
||||||
return new HttpSessionEventPublisher();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
package cn.van.business.controller;
|
|
||||||
|
|
||||||
import cn.van.business.model.user.UserInfo;
|
|
||||||
import cn.van.business.util.JwtUtils;
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
import org.springframework.web.servlet.ModelAndView;
|
|
||||||
import org.springframework.web.servlet.mvc.AbstractController;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class BaseController extends AbstractController {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private JwtUtils jwtUtils;
|
|
||||||
|
|
||||||
// 获取当前用户信息
|
|
||||||
protected UserInfo getCurrentUser(HttpServletRequest request) {
|
|
||||||
String token = extractToken(request);
|
|
||||||
if (token != null && jwtUtils.validateToken(token)) {
|
|
||||||
String username = jwtUtils.extractUsername(token);
|
|
||||||
// 从数据库获取用户信息
|
|
||||||
return loadUserInfo(username);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查用户是否已登录
|
|
||||||
protected boolean isLogin(HttpServletRequest request) {
|
|
||||||
return getCurrentUser(request) != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取当前用户ID
|
|
||||||
protected String getCurrentUserId(HttpServletRequest request) {
|
|
||||||
UserInfo user = getCurrentUser(request);
|
|
||||||
return user != null ? user.getId() : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取当前用户角色
|
|
||||||
protected List<String> getCurrentUserRoles(HttpServletRequest request) {
|
|
||||||
UserInfo user = getCurrentUser(request);
|
|
||||||
return user != null ? user.getRoles() : Collections.emptyList();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 提取token
|
|
||||||
private String extractToken(HttpServletRequest request) {
|
|
||||||
String bearerToken = request.getHeader("Authorization");
|
|
||||||
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
|
|
||||||
return bearerToken.substring(7);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 加载用户信息
|
|
||||||
private UserInfo loadUserInfo(String username) {
|
|
||||||
// 实现用户信息加载
|
|
||||||
return new UserInfo();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
package cn.van.business.controller.jd;
|
|
||||||
|
|
||||||
import cn.van.business.filter.RequireAuth;
|
|
||||||
import cn.van.business.model.ApiResponse;
|
|
||||||
import cn.van.business.model.jd.ProductOrder;
|
|
||||||
import cn.van.business.mq.MessageProducerService;
|
|
||||||
import cn.van.business.repository.ProductOrderRepository;
|
|
||||||
import cn.van.business.util.JDUtil;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.ResponseBody;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Leo
|
|
||||||
* @version 1.0
|
|
||||||
* @create 2024/11/7 13:39
|
|
||||||
* @description:
|
|
||||||
*/
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/recordOrder")
|
|
||||||
@RequireAuth
|
|
||||||
public class OrderController {
|
|
||||||
|
|
||||||
public static String TOKEN = "dd7f298cc465e7a9791796ae1303a1f6c031a18d4894ecd9e75d44fdb0ef32d80a49e4c5811b98d8ac65d73d22a54083630d5329c7c9de2276317669ecb2e544";
|
|
||||||
@Autowired
|
|
||||||
private MessageProducerService messageProducerService;
|
|
||||||
@Autowired
|
|
||||||
private ProductOrderRepository productOrderRepository;
|
|
||||||
|
|
||||||
@RequestMapping("/list")
|
|
||||||
@ResponseBody
|
|
||||||
public ApiResponse list() throws Exception {
|
|
||||||
try {
|
|
||||||
|
|
||||||
List<ProductOrder> all = productOrderRepository.findAll();
|
|
||||||
return ApiResponse.success(all);
|
|
||||||
} catch (Exception e) {
|
|
||||||
return ApiResponse.error(500, "Server Error: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
package cn.van.business.controller.jd.erp;
|
|
||||||
|
|
||||||
import cn.hutool.http.HttpRequest;
|
|
||||||
import cn.hutool.http.HttpResponse;
|
|
||||||
import cn.van.business.erp.request.AuthorizeListQueryRequest;
|
|
||||||
import cn.van.business.erp.request.ERPAccount;
|
|
||||||
import cn.van.business.erp.request.ProductListQueryRequest;
|
|
||||||
import com.alibaba.fastjson2.JSONObject;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Leo
|
|
||||||
* @version 1.0
|
|
||||||
* @create 2025/6/17 21:06
|
|
||||||
* @description:
|
|
||||||
*/
|
|
||||||
public class TestController {
|
|
||||||
public static void main(String[] args) {
|
|
||||||
//AuthorizeListQueryRequest authorizeListQueryRequest = new AuthorizeListQueryRequest(ERPAccount.ACCOUNT_HUGE, new JSONObject());
|
|
||||||
//String responseBody = authorizeListQueryRequest.getResponseBody();
|
|
||||||
//System.out.println(responseBody);
|
|
||||||
System.out.println("--------------");
|
|
||||||
ProductListQueryRequest productListQueryRequest = new ProductListQueryRequest(ERPAccount.ACCOUNT_HUGE);
|
|
||||||
JSONObject jsonObject = new JSONObject();
|
|
||||||
jsonObject.put("page_no", 1);
|
|
||||||
jsonObject.put("page_size", 1);
|
|
||||||
productListQueryRequest.setRequestBody(jsonObject);
|
|
||||||
String responseBody1 = productListQueryRequest.getResponseBody();
|
|
||||||
System.out.println(responseBody1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,146 +0,0 @@
|
|||||||
package cn.van.business.controller.login;
|
|
||||||
|
|
||||||
import cn.van.business.model.ApiResponse;
|
|
||||||
import cn.van.business.model.user.LoginRequest;
|
|
||||||
import cn.van.business.model.user.LoginResponse;
|
|
||||||
import cn.van.business.service.CaptchaService;
|
|
||||||
import cn.van.business.util.JwtUtils;
|
|
||||||
import cn.van.business.util.WXUtil;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.security.authentication.AuthenticationManager;
|
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.core.AuthenticationException;
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/auth")
|
|
||||||
public class AuthController {
|
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(AuthController.class);
|
|
||||||
|
|
||||||
|
|
||||||
private final CaptchaService captchaService;
|
|
||||||
private final JwtUtils jwtUtils;
|
|
||||||
private final AuthenticationManager authenticationManager;
|
|
||||||
private final StringRedisTemplate redisTemplate;
|
|
||||||
|
|
||||||
public AuthController(CaptchaService captchaService, JwtUtils jwtUtils, AuthenticationManager authenticationManager, StringRedisTemplate redisTemplate) {
|
|
||||||
this.captchaService = captchaService;
|
|
||||||
this.jwtUtils = jwtUtils;
|
|
||||||
this.authenticationManager = authenticationManager;
|
|
||||||
this.redisTemplate = redisTemplate;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final String USER_TOKEN_PREFIX = "user:token:";
|
|
||||||
|
|
||||||
@PostMapping("/login")
|
|
||||||
public ApiResponse login(@RequestBody LoginRequest request) {
|
|
||||||
logger.info("用户登录");
|
|
||||||
logger.info("用户名:{}", request.getUsername());
|
|
||||||
logger.info("密码:{}", request.getPassword());
|
|
||||||
logger.info("验证码:{}", request.getCaptcha());
|
|
||||||
logger.info("生成的验证码:{}", request.getGeneratedCaptcha());
|
|
||||||
// 1. 基础校验
|
|
||||||
if (StringUtils.isBlank(request.getUsername()) || StringUtils.isBlank(request.getPassword())) {
|
|
||||||
logger.error("用户名或密码不能为空");
|
|
||||||
return ApiResponse.badRequest();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. 验证码校验
|
|
||||||
if (!captchaService.validateCaptcha(request.getCaptcha(), request.getGeneratedCaptcha())) {
|
|
||||||
logger.error("验证码错误");
|
|
||||||
return ApiResponse.badRequest();
|
|
||||||
}
|
|
||||||
// 在验证码校验后、认证前添加防爆破逻辑
|
|
||||||
String loginFailKey = "login:fail:" + request.getUsername();
|
|
||||||
Long failCount = redisTemplate.opsForValue().increment(loginFailKey);
|
|
||||||
if (failCount != null && failCount > 5) {
|
|
||||||
logger.error("尝试次数过多,请稍后再试");
|
|
||||||
return ApiResponse.badRequest();
|
|
||||||
}else {
|
|
||||||
redisTemplate.expire(loginFailKey, 5, TimeUnit.MINUTES);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. 用户认证(Spring Security 标准流程)
|
|
||||||
logger.info("3. 用户认证(Spring Security 标准流程)");
|
|
||||||
try {
|
|
||||||
Authentication authentication = authenticationManager.authenticate(
|
|
||||||
new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword()));
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error("用户认证失败:{}", e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. 生成 JWT Token
|
|
||||||
logger.info("用户认证成功");
|
|
||||||
String token = jwtUtils.generateToken(request.getUsername());
|
|
||||||
String refreshToken = generateRefreshToken(request.getUsername());
|
|
||||||
logger.info("生成 JWT Token:{}", token);
|
|
||||||
|
|
||||||
// 5. 存入 Redis
|
|
||||||
saveUserToken(request.getUsername(), token, jwtUtils.getExpiration(token));
|
|
||||||
|
|
||||||
// 6. 构造响应
|
|
||||||
LoginResponse response = new LoginResponse();
|
|
||||||
response.setToken(token);
|
|
||||||
response.setRefreshToken(refreshToken);
|
|
||||||
response.setUsername(request.getUsername());
|
|
||||||
|
|
||||||
return ApiResponse.success(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@PostMapping("/logout")
|
|
||||||
public ApiResponse logout(@RequestHeader("Authorization") String token) {
|
|
||||||
String actualToken = token.startsWith("Bearer ") ? token.substring(7) : token;
|
|
||||||
String username = jwtUtils.extractUsername(actualToken);
|
|
||||||
String userKey = USER_TOKEN_PREFIX + username;
|
|
||||||
|
|
||||||
// 从 Redis 删除用户 Token
|
|
||||||
redisTemplate.delete(userKey);
|
|
||||||
|
|
||||||
// 添加 Token 到黑名单
|
|
||||||
addToBlacklist(actualToken, jwtUtils.getRemainingExpirationTime(actualToken));
|
|
||||||
|
|
||||||
return ApiResponse.success();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@GetMapping("/captcha")
|
|
||||||
public ApiResponse<String> getCaptcha() throws Exception {
|
|
||||||
logger.info("获取验证码");
|
|
||||||
String captchaImage = captchaService.generateCaptchaImage();
|
|
||||||
return ApiResponse.success(captchaImage);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将用户 Token 存入 Redis,并设置相同有效期
|
|
||||||
*/
|
|
||||||
private void saveUserToken(String username, String token, long expiration) {
|
|
||||||
String key = USER_TOKEN_PREFIX + username;
|
|
||||||
redisTemplate.opsForValue().set(key, token, expiration, TimeUnit.MILLISECONDS);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将 Token 加入黑名单
|
|
||||||
*/
|
|
||||||
private void addToBlacklist(String token, long remainingTime) {
|
|
||||||
String blacklistKey = "blacklist:token:" + token;
|
|
||||||
redisTemplate.opsForValue().set(blacklistKey, "invalid", remainingTime, TimeUnit.MILLISECONDS);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 生成刷新 Token(Refresh Token)
|
|
||||||
*/
|
|
||||||
private String generateRefreshToken(String username) {
|
|
||||||
return jwtUtils.generateToken(username);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
package cn.van.business.controller.login;
|
|
||||||
|
|
||||||
import cn.van.business.service.CaptchaService;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/captcha")
|
|
||||||
public class CaptchaController {
|
|
||||||
|
|
||||||
private final CaptchaService captchaService;
|
|
||||||
|
|
||||||
public CaptchaController(CaptchaService captchaService) {
|
|
||||||
this.captchaService = captchaService;
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/generate")
|
|
||||||
public ResponseEntity<String> generateCaptcha() throws Exception {
|
|
||||||
String base64Image = captchaService.generateCaptchaImage();
|
|
||||||
return ResponseEntity.ok(base64Image);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
package cn.van.business.filter;
|
|
||||||
|
|
||||||
import cn.van.business.service.impl.UserDetailsServiceImpl;
|
|
||||||
import cn.van.business.util.JwtUtils;
|
|
||||||
import jakarta.servlet.FilterChain;
|
|
||||||
import jakarta.servlet.ServletException;
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
import lombok.NonNull;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
|
||||||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
import org.springframework.web.filter.OncePerRequestFilter;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class JwtAuthFilter extends OncePerRequestFilter {
|
|
||||||
|
|
||||||
private final JwtUtils jwtUtils;
|
|
||||||
private final UserDetailsServiceImpl userDetailsService;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doFilterInternal(@NonNull HttpServletRequest request,
|
|
||||||
@NonNull HttpServletResponse response,
|
|
||||||
@NonNull FilterChain filterChain)
|
|
||||||
throws ServletException, IOException {
|
|
||||||
try {
|
|
||||||
logger.debug("JwtAuthFilter.doFilterInternal");
|
|
||||||
String jwt = parseJwt(request);
|
|
||||||
if (jwt != null && jwtUtils.validateToken(jwt)) {
|
|
||||||
String username = jwtUtils.extractUsername(jwt);
|
|
||||||
|
|
||||||
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
|
|
||||||
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
|
|
||||||
userDetails, null, userDetails.getAuthorities());
|
|
||||||
|
|
||||||
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
|
||||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error("无法认证用户: {}");
|
|
||||||
}
|
|
||||||
|
|
||||||
filterChain.doFilter(request, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String parseJwt(HttpServletRequest request) {
|
|
||||||
String headerAuth = request.getHeader("Authorization");
|
|
||||||
|
|
||||||
if (StringUtils.hasText(headerAuth) && headerAuth.startsWith("Bearer ")) {
|
|
||||||
return headerAuth.substring(7);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package cn.van.business.filter;
|
|
||||||
|
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
|
||||||
|
|
||||||
import java.lang.annotation.ElementType;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
|
|
||||||
@Target({ElementType.METHOD, ElementType.TYPE})
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
|
||||||
@PreAuthorize("hasAnyRole('ADMIN', 'USER')")
|
|
||||||
public @interface RequireAuth {}
|
|
||||||
48
src/main/java/cn/van/business/model/pl/TaobaoComment.java
Normal file
48
src/main/java/cn/van/business/model/pl/TaobaoComment.java
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package cn.van.business.model.pl;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Van
|
||||||
|
* @version 1.0
|
||||||
|
* @create 2025/7/6
|
||||||
|
* @description 淘宝评论实体类
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "taobao_comments")
|
||||||
|
@Data
|
||||||
|
public class TaobaoComment {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Integer id;
|
||||||
|
|
||||||
|
@Column(name = "product_id", nullable = false, length = 255)
|
||||||
|
private String productId;
|
||||||
|
|
||||||
|
@Column(name = "user_name", length = 255)
|
||||||
|
private String userName;
|
||||||
|
|
||||||
|
@Lob
|
||||||
|
@Column(name = "comment_text")
|
||||||
|
private String commentText;
|
||||||
|
|
||||||
|
@Column(name = "comment_id", length = 255, unique = false)
|
||||||
|
private String commentId;
|
||||||
|
|
||||||
|
@Lob
|
||||||
|
@Column(name = "picture_urls")
|
||||||
|
private String pictureUrls;
|
||||||
|
|
||||||
|
@Column(name = "created_at")
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
|
||||||
|
@Column(name = "comment_date", length = 255)
|
||||||
|
private String commentDate;
|
||||||
|
|
||||||
|
@Column(name = "is_use", columnDefinition = "int default 0")
|
||||||
|
private Integer isUse;
|
||||||
|
}
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
CREATE TABLE product_order
|
|
||||||
(
|
|
||||||
id BIGINT AUTO_INCREMENT NOT NULL,
|
|
||||||
sku_name VARCHAR(255) NULL,
|
|
||||||
sku_type INT NULL,
|
|
||||||
order_id VARCHAR(255) NULL,
|
|
||||||
order_time datetime NULL,
|
|
||||||
order_account VARCHAR(255) NULL,
|
|
||||||
is_reviewed BIT(1) NULL,
|
|
||||||
review_time datetime NULL,
|
|
||||||
is_cashback_received BIT(1) NULL,
|
|
||||||
cashback_time datetime NULL,
|
|
||||||
recipient_name VARCHAR(255) NULL,
|
|
||||||
recipient_phone VARCHAR(255) NULL,
|
|
||||||
recipient_address VARCHAR(255) NULL,
|
|
||||||
who_order VARCHAR(255) NULL,
|
|
||||||
CONSTRAINT pk_product_order PRIMARY KEY (id)
|
|
||||||
);
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
package cn.van.business.model.user;
|
|
||||||
|
|
||||||
import jakarta.persistence.Column;
|
|
||||||
import jakarta.persistence.Entity;
|
|
||||||
import jakarta.persistence.GeneratedValue;
|
|
||||||
import jakarta.persistence.GenerationType;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.Setter;
|
|
||||||
import jakarta.persistence.*;
|
|
||||||
|
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Leo
|
|
||||||
* @version 1.0
|
|
||||||
* @create 2025/6/16 10:55
|
|
||||||
* @description:
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
@Entity
|
|
||||||
@Table(name = "admin_users")
|
|
||||||
public class AdminUser {
|
|
||||||
@Id
|
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
|
||||||
private Long id;
|
|
||||||
|
|
||||||
@Column(unique = true, nullable = false)
|
|
||||||
private String username;
|
|
||||||
|
|
||||||
private String password;
|
|
||||||
|
|
||||||
private String nickname;
|
|
||||||
|
|
||||||
private String avatarUrl;
|
|
||||||
|
|
||||||
private String roles; // 角色列表,可用逗号分隔
|
|
||||||
|
|
||||||
private LocalDateTime lastLoginTime;
|
|
||||||
|
|
||||||
private LocalDateTime createTime;
|
|
||||||
|
|
||||||
private LocalDateTime updateTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package cn.van.business.model.user;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.Setter;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
public class LoginRequest {
|
|
||||||
private String username;
|
|
||||||
private String password;
|
|
||||||
private String captcha; // 验证码字段(如果需要)
|
|
||||||
private String generatedCaptcha;
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
package cn.van.business.model.user;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.Setter;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
public class LoginResponse {
|
|
||||||
private String token;
|
|
||||||
private Long expiresIn;
|
|
||||||
private String refreshToken;
|
|
||||||
private UserInfo userInfo;
|
|
||||||
private String username;
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
package cn.van.business.model.user;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.Setter;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
public class UserInfo {
|
|
||||||
private String username;
|
|
||||||
private String nickname;
|
|
||||||
private String id;
|
|
||||||
private List<String> roles;
|
|
||||||
private String avatarUrl;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package cn.van.business.repository;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
import cn.van.business.model.pl.TaobaoComment;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Van
|
||||||
|
* @version 1.0
|
||||||
|
* @create 2025/7/6
|
||||||
|
* @description 淘宝评论数据访问层接口
|
||||||
|
*/
|
||||||
|
@Repository
|
||||||
|
public interface TaobaoCommentRepository extends JpaRepository<TaobaoComment, Integer> {
|
||||||
|
|
||||||
|
List<TaobaoComment> findByProductIdAndIsUseNotAndPictureUrlsIsNotNull(String productId, Integer isUse);
|
||||||
|
|
||||||
|
List<TaobaoComment> findByProductIdAndPictureUrlsIsNotNull(String productId);
|
||||||
|
}
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
package cn.van.business.service;
|
|
||||||
|
|
||||||
import cn.hutool.crypto.digest.MD5;
|
|
||||||
import cn.van.business.model.user.AdminUser;
|
|
||||||
import cn.van.business.util.Util;
|
|
||||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class AdminUserService {
|
|
||||||
|
|
||||||
|
|
||||||
// 模拟数据库存储
|
|
||||||
private static final Map<String, AdminUser> users = new HashMap<>();
|
|
||||||
|
|
||||||
static {
|
|
||||||
// 初始化一个测试用户(生产环境应从数据库加载)
|
|
||||||
AdminUser user = new AdminUser();
|
|
||||||
user.setUsername("van");
|
|
||||||
String password = Util.md5("LK.807878712");
|
|
||||||
user.setPassword(new BCryptPasswordEncoder().encode(password));
|
|
||||||
users.put("van", user);
|
|
||||||
}
|
|
||||||
|
|
||||||
public AdminUser findByUsername(String username) {
|
|
||||||
return users.get(username);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
package cn.van.business.service;
|
|
||||||
|
|
||||||
import com.google.code.kaptcha.Producer;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
|
||||||
import java.awt.image.BufferedImage;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.util.Base64;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class CaptchaService {
|
|
||||||
|
|
||||||
private final Producer kaptchaProducer;
|
|
||||||
|
|
||||||
public CaptchaService(Producer kaptchaProducer) {
|
|
||||||
this.kaptchaProducer = kaptchaProducer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String generateCaptchaImage() throws Exception {
|
|
||||||
String text = kaptchaProducer.createText();
|
|
||||||
BufferedImage image = kaptchaProducer.createImage(text);
|
|
||||||
|
|
||||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
|
||||||
ImageIO.write(image, "PNG", outputStream);
|
|
||||||
return Base64.getEncoder().encodeToString(outputStream.toByteArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean validateCaptcha(String userInput, String generatedCaptcha) {
|
|
||||||
return userInput.equalsIgnoreCase("Succ");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
package cn.van.business.service.impl;
|
|
||||||
|
|
||||||
import cn.van.business.model.user.AdminUser;
|
|
||||||
import cn.van.business.service.AdminUserService;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
|
||||||
import org.springframework.security.core.userdetails.User;
|
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
|
||||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
|
||||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class UserDetailsServiceImpl implements UserDetailsService {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private AdminUserService adminUserService;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
|
||||||
AdminUser user = adminUserService.findByUsername(username);
|
|
||||||
if (user == null) {
|
|
||||||
throw new UsernameNotFoundException("用户不存在");
|
|
||||||
}
|
|
||||||
|
|
||||||
return User.builder()
|
|
||||||
.username(user.getUsername())
|
|
||||||
.password(user.getPassword())
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
package cn.van.business.util;
|
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
import org.springframework.security.core.AuthenticationException;
|
|
||||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
public class AuthEntryPoint implements AuthenticationEntryPoint {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
|
|
||||||
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "需要登录认证");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -548,10 +548,10 @@ public class JDScheduleJob {
|
|||||||
hashOps.putAll(key, hours.stream().collect(Collectors.toMap(h -> h, h -> "1")));
|
hashOps.putAll(key, hours.stream().collect(Collectors.toMap(h -> h, h -> "1")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Scheduled(cron = "0 0 0,8 * * ?")
|
@Scheduled(cron = "0 0 0 * * ?")
|
||||||
public void checkGiftCouponsExpiry() {
|
public void checkGiftCouponsExpiry() {
|
||||||
Set<String> keys = redisTemplate.keys("gift_coupon:*");
|
Set<String> keys = redisTemplate.keys("gift_coupon:*");
|
||||||
if (keys == null || keys.isEmpty()) return;
|
if (keys.isEmpty()) return;
|
||||||
|
|
||||||
for (String key : keys) {
|
for (String key : keys) {
|
||||||
Map<Object, Object> entries = redisTemplate.opsForHash().entries(key);
|
Map<Object, Object> entries = redisTemplate.opsForHash().entries(key);
|
||||||
@@ -578,6 +578,7 @@ public class JDScheduleJob {
|
|||||||
if (isAboutToExpire) {
|
if (isAboutToExpire) {
|
||||||
String message = String.format("[礼金提醒]\n商品:%s\n礼金Key:%s\n类型:%s\n将在 %s 过期", skuName, giftKey, "g".equals(owner) ? "自营" : "POP", expireTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")));
|
String message = String.format("[礼金提醒]\n商品:%s\n礼金Key:%s\n类型:%s\n将在 %s 过期", skuName, giftKey, "g".equals(owner) ? "自营" : "POP", expireTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")));
|
||||||
wxUtil.sendTextMessage(WXUtil.default_super_admin_wxid, message, 1, "bot", false);
|
wxUtil.sendTextMessage(WXUtil.default_super_admin_wxid, message, 1, "bot", false);
|
||||||
|
redisTemplate.delete(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,11 @@ import cn.hutool.http.HttpResponse;
|
|||||||
import cn.van.business.model.jd.JDOrder;
|
import cn.van.business.model.jd.JDOrder;
|
||||||
import cn.van.business.model.jd.OrderRow;
|
import cn.van.business.model.jd.OrderRow;
|
||||||
import cn.van.business.model.pl.Comment;
|
import cn.van.business.model.pl.Comment;
|
||||||
|
import cn.van.business.model.pl.TaobaoComment;
|
||||||
import cn.van.business.repository.CommentRepository;
|
import cn.van.business.repository.CommentRepository;
|
||||||
import cn.van.business.repository.JDOrderRepository;
|
import cn.van.business.repository.JDOrderRepository;
|
||||||
import cn.van.business.repository.OrderRowRepository;
|
import cn.van.business.repository.OrderRowRepository;
|
||||||
|
import cn.van.business.repository.TaobaoCommentRepository;
|
||||||
import cn.van.business.util.ds.DeepSeekClientUtil;
|
import cn.van.business.util.ds.DeepSeekClientUtil;
|
||||||
import cn.van.business.util.ds.GPTClientUtil;
|
import cn.van.business.util.ds.GPTClientUtil;
|
||||||
import com.alibaba.fastjson2.JSON;
|
import com.alibaba.fastjson2.JSON;
|
||||||
@@ -81,7 +83,10 @@ public class JDUtil {
|
|||||||
private static final Logger logger = LoggerFactory.getLogger(JDUtil.class);
|
private static final Logger logger = LoggerFactory.getLogger(JDUtil.class);
|
||||||
private static final String INTERACTION_STATE_PREFIX = "interaction_state:";
|
private static final String INTERACTION_STATE_PREFIX = "interaction_state:";
|
||||||
private static final String PRODUCT_TYPE_MAP_PREFIX = "product_type_map";
|
private static final String PRODUCT_TYPE_MAP_PREFIX = "product_type_map";
|
||||||
|
private static final String PRODUCT_TYPE_MAP_PREFIX_TB = "product_type_map_tb";
|
||||||
private static HashMap<String, String> productTypeMap = new HashMap<>();
|
private static HashMap<String, String> productTypeMap = new HashMap<>();
|
||||||
|
private static HashMap<String, String> productTypeMapTB = new HashMap<>();
|
||||||
|
|
||||||
private static final String COMMENT_TEMPLATES_DS = "我需要为我的商品模拟一些商品评论。你协助我生成2条不同的评价内容,京东商品评价的风格,每条评价100字左右,要基于原来的评论稍作修改,不要更换产品类型,只需要好评。不需要太浮夸,也不要太像ai生成,尽量模拟真实客户评价,不要提到以旧换新和国家补贴,只要回复我评论的内容就可以。这个是给你参考的其他真实用户的评论:";
|
private static final String COMMENT_TEMPLATES_DS = "我需要为我的商品模拟一些商品评论。你协助我生成2条不同的评价内容,京东商品评价的风格,每条评价100字左右,要基于原来的评论稍作修改,不要更换产品类型,只需要好评。不需要太浮夸,也不要太像ai生成,尽量模拟真实客户评价,不要提到以旧换新和国家补贴,只要回复我评论的内容就可以。这个是给你参考的其他真实用户的评论:";
|
||||||
private static final long TIMEOUT_MINUTES = 30;
|
private static final long TIMEOUT_MINUTES = 30;
|
||||||
private static final String WENAN_FANAN_LQD = "提供方法自己下单\n" + "全程都是自己的账号下单\n" + "标价就是下单的到手价\n" + "本人有耐心,会一步一步提供教程\n" + "以后有什么质量问题都是用自己的账号走京东售后\n" + "\n" + "更新\n" + "\n" + "用你自己的账号下单\n" + "官方店铺 提供方法自己下单\n" + "————————————————————\n" + "同行可长久合作,可提供神级家电线报\n" + "\n" + "配合家电线报可以自己下单,不用找代购和代下,订单和利润都掌握在自己手中。\n" + "\n" + "一次入会永久使用,包含家电帮,雷神价,韭菜帮,河南&湖南帮,各种暗号帮后返等内部独家家电线报\n" + "\n" + "JD采销采购不定时发放独家优惠券\n" + "\n" + "基本上你能看到的京东家电低价都是从这些渠道里面出来。\n" + "\n" + "2025年家电项目新方向,配合家电线报下单,秒省1K+。";
|
private static final String WENAN_FANAN_LQD = "提供方法自己下单\n" + "全程都是自己的账号下单\n" + "标价就是下单的到手价\n" + "本人有耐心,会一步一步提供教程\n" + "以后有什么质量问题都是用自己的账号走京东售后\n" + "\n" + "更新\n" + "\n" + "用你自己的账号下单\n" + "官方店铺 提供方法自己下单\n" + "————————————————————\n" + "同行可长久合作,可提供神级家电线报\n" + "\n" + "配合家电线报可以自己下单,不用找代购和代下,订单和利润都掌握在自己手中。\n" + "\n" + "一次入会永久使用,包含家电帮,雷神价,韭菜帮,河南&湖南帮,各种暗号帮后返等内部独家家电线报\n" + "\n" + "JD采销采购不定时发放独家优惠券\n" + "\n" + "基本上你能看到的京东家电低价都是从这些渠道里面出来。\n" + "\n" + "2025年家电项目新方向,配合家电线报下单,秒省1K+。";
|
||||||
@@ -122,6 +127,7 @@ public class JDUtil {
|
|||||||
private final OrderRowRepository orderRowRepository;
|
private final OrderRowRepository orderRowRepository;
|
||||||
private final CommentRepository commentRepository;
|
private final CommentRepository commentRepository;
|
||||||
private JDOrderRepository jdOrderRepository;
|
private JDOrderRepository jdOrderRepository;
|
||||||
|
private final TaobaoCommentRepository taobaoCommentRepository;
|
||||||
|
|
||||||
private final OrderUtil orderUtil;
|
private final OrderUtil orderUtil;
|
||||||
private final DeepSeekClientUtil deepSeekClientUtil;
|
private final DeepSeekClientUtil deepSeekClientUtil;
|
||||||
@@ -136,11 +142,12 @@ public class JDUtil {
|
|||||||
|
|
||||||
// 构造函数中注入StringRedisTemplate
|
// 构造函数中注入StringRedisTemplate
|
||||||
@Autowired
|
@Autowired
|
||||||
public JDUtil(JDOrderRepository jdOrderRepository, StringRedisTemplate redisTemplate, OrderRowRepository orderRowRepository, WXUtil wxUtil, OrderUtil orderUtil, DeepSeekClientUtil deepSeekClientUtil, CommentRepository commentRepository, GPTClientUtil gptClientUtil) {
|
public JDUtil(JDOrderRepository jdOrderRepository, StringRedisTemplate redisTemplate, OrderRowRepository orderRowRepository, WXUtil wxUtil, TaobaoCommentRepository taobaoCommentRepository, OrderUtil orderUtil, DeepSeekClientUtil deepSeekClientUtil, CommentRepository commentRepository, GPTClientUtil gptClientUtil) {
|
||||||
this.jdOrderRepository = jdOrderRepository;
|
this.jdOrderRepository = jdOrderRepository;
|
||||||
this.redisTemplate = redisTemplate;
|
this.redisTemplate = redisTemplate;
|
||||||
this.orderRowRepository = orderRowRepository;
|
this.orderRowRepository = orderRowRepository;
|
||||||
this.wxUtil = wxUtil;
|
this.wxUtil = wxUtil;
|
||||||
|
this.taobaoCommentRepository = taobaoCommentRepository;
|
||||||
this.orderUtil = orderUtil;
|
this.orderUtil = orderUtil;
|
||||||
this.deepSeekClientUtil = deepSeekClientUtil;
|
this.deepSeekClientUtil = deepSeekClientUtil;
|
||||||
this.commentRepository = commentRepository;
|
this.commentRepository = commentRepository;
|
||||||
@@ -149,10 +156,11 @@ public class JDUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void handleProductWithJF() {
|
private void handleProductWithJF() {
|
||||||
productWithJF.put("ZQD130F-EB130", "https://u.jd.com/3GPePLu");
|
productWithJF.put("ZQD130F-EB130", "https://u.jd.com/21x8qP6");
|
||||||
productWithJF.put("ZQD130F-EB130B", "https://u.jd.com/3gPeGf6");
|
productWithJF.put("ZQD130F-EB130B", "https://u.jd.com/2ax86hv");
|
||||||
productWithJF.put("ZQD180F-EB200", "https://u.jd.com/3OPe8m7");
|
productWithJF.put("ZQD180F-EB200", "https://u.jd.com/26x8t4M");
|
||||||
productWithJF.put("ZQD150F-EB150", "https://u.jd.com/3rPevYD");
|
productWithJF.put("ZQD150F-EB150", "https://u.jd.com/21x8M6G");
|
||||||
|
productWithJF.put("CXW-358-E900C51", "https://u.jd.com/2gx8MvZ");
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<OrderRow> filterOrdersByDate(List<OrderRow> orderRows, int daysBack) {
|
private List<OrderRow> filterOrdersByDate(List<OrderRow> orderRows, int daysBack) {
|
||||||
@@ -1772,13 +1780,19 @@ public class JDUtil {
|
|||||||
// 思路是将地址解析出来,保存到 Redis 中,并设置过期时间为 24 小时。
|
// 思路是将地址解析出来,保存到 Redis 中,并设置过期时间为 24 小时。
|
||||||
String address = split[5].trim();
|
String address = split[5].trim();
|
||||||
String addressKey = "address:" + address;
|
String addressKey = "address:" + address;
|
||||||
if (redisTemplate.opsForValue().get(addressKey) != null) {
|
String addressRedisValue = redisTemplate.opsForValue().get(addressKey);
|
||||||
for (int i = 0; i < 5; i++) {
|
if (addressRedisValue != null) {
|
||||||
wxUtil.sendTextMessage(fromWxid, "[炸弹] [炸弹] [炸弹] 此地址已经存在,请勿重复生成订单 [炸弹] [炸弹] [炸弹] ", 1, fromWxid, false);
|
logger.info("address {}", addressKey);
|
||||||
|
if (addressRedisValue.contains("李波") || addressRedisValue.contains("吴胜硕") || addressRedisValue.contains("小硕硕")) {
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
wxUtil.sendTextMessage(fromWxid, "[炸弹] [炸弹] [炸弹] 此地址已经存在,请勿重复生成订单 [炸弹] [炸弹] [炸弹] ", 1, fromWxid, false);
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
redisTemplate.opsForValue().set(addressKey, "1", 1, TimeUnit.DAYS);
|
redisTemplate.opsForValue().set(addressKey, address, 1, TimeUnit.DAYS);
|
||||||
|
|
||||||
// 先一次性增加计数器,并设置到 Redis(原子性操作)
|
// 先一次性增加计数器,并设置到 Redis(原子性操作)
|
||||||
int startCount = count + 1;
|
int startCount = count + 1;
|
||||||
@@ -1836,7 +1850,7 @@ public class JDUtil {
|
|||||||
//String value = entry.getValue();
|
//String value = entry.getValue();
|
||||||
productTypeStr.append(k).append("\n");
|
productTypeStr.append(k).append("\n");
|
||||||
}
|
}
|
||||||
wxUtil.sendTextMessage(fromWxid, "请选择要生成的评论类型\n(烟灶套餐 晒图 单烟机型号,新型号联系管理员添加(970用C61的图):\n\n" + productTypeStr, 1, fromWxid, true);
|
wxUtil.sendTextMessage(fromWxid, "请选择要生成的评论类型\n(烟灶套餐 晒图 单烟机型号,新型号联系管理员:\n\n" + productTypeStr, 1, fromWxid, true);
|
||||||
logger.info("进入生成评论流程 - 用户: {}", fromWxid);
|
logger.info("进入生成评论流程 - 用户: {}", fromWxid);
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -1874,6 +1888,41 @@ public class JDUtil {
|
|||||||
redisTemplate.opsForHash().delete(PRODUCT_TYPE_MAP_PREFIX, key);
|
redisTemplate.opsForHash().delete(PRODUCT_TYPE_MAP_PREFIX, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public HashMap<String, String> getProductTypeMapForTB() {
|
||||||
|
Map<Object, Object> rawMap = redisTemplate.opsForHash().entries(PRODUCT_TYPE_MAP_PREFIX_TB);
|
||||||
|
|
||||||
|
if (!rawMap.isEmpty()) {
|
||||||
|
productTypeMapTB.clear();
|
||||||
|
for (Map.Entry<Object, Object> entry : rawMap.entrySet()) {
|
||||||
|
productTypeMapTB.put(entry.getKey().toString(), entry.getValue().toString());
|
||||||
|
}
|
||||||
|
// 排序
|
||||||
|
productTypeMapTB = productTypeMapTB.entrySet().stream().sorted(Map.Entry.comparingByKey()).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
|
||||||
|
return productTypeMapTB;
|
||||||
|
} else {
|
||||||
|
logger.warn("Redis 中未找到键为 {} 的 Hash 数据", PRODUCT_TYPE_MAP_PREFIX_TB);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据原始的 product_id 查询淘宝映射后的 ID
|
||||||
|
*
|
||||||
|
* @param rawProductId 原始 product_id
|
||||||
|
* @return 映射后的 ID,如果不存在则返回原值
|
||||||
|
*/
|
||||||
|
public static String getMappedProductId(String rawProductId) {
|
||||||
|
// 确保 map 已加载(可选:调用一次 getProductTypeMapForTB())
|
||||||
|
if (productTypeMapTB == null || productTypeMapTB.isEmpty()) {
|
||||||
|
// 可以在这里自动加载(需传入 RedisTemplate 或通过 Spring 注入)
|
||||||
|
// 示例:JDUtil.getProductTypeMapForTB();
|
||||||
|
return rawProductId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查找映射值
|
||||||
|
return productTypeMapTB.getOrDefault(rawProductId, rawProductId);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理生成评论流程中的用户交互
|
* 处理生成评论流程中的用户交互
|
||||||
@@ -1896,16 +1945,18 @@ public class JDUtil {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成评论内容
|
* 根据商品类型生成评论内容,并优先使用京东评论,若无则使用淘宝评论
|
||||||
|
*
|
||||||
|
* @param fromWxid 微信用户ID
|
||||||
|
* @param productType 商品类型(如:烟灶套餐、单烟机等)
|
||||||
*/
|
*/
|
||||||
private synchronized void generateComment(String fromWxid, String productType) {
|
private synchronized void generateComment(String fromWxid, String productType) {
|
||||||
|
|
||||||
//wxUtil.sendTextMessage(fromWxid, "已接收到评论生成指令,等候过程请勿重复输入", 1, fromWxid, true);
|
|
||||||
int allCommentCount = 0;
|
int allCommentCount = 0;
|
||||||
int usedCommentCount = 0;
|
int usedCommentCount = 0;
|
||||||
int canUseComentCount = 0;
|
int canUseComentCount = 0;
|
||||||
int addCommentCount = 0;
|
int addCommentCount = 0;
|
||||||
// 获取产品ID
|
boolean isTb = false;
|
||||||
|
|
||||||
getProductTypeMap();
|
getProductTypeMap();
|
||||||
String product_id = productTypeMap.get(productType);
|
String product_id = productTypeMap.get(productType);
|
||||||
if (product_id == null || product_id.isEmpty()) {
|
if (product_id == null || product_id.isEmpty()) {
|
||||||
@@ -1913,95 +1964,167 @@ public class JDUtil {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从数据库获取可用评论
|
// 获取本地可用的京东评论
|
||||||
List<Comment> availableComments = commentRepository.findByProductIdAndIsUseNotAndPictureUrlsIsNotNull(product_id, 1);
|
List<Comment> availableComments = commentRepository.findByProductIdAndIsUseNotAndPictureUrlsIsNotNull(product_id, 1);
|
||||||
List<Comment> usedComments = commentRepository.findByProductIdAndIsUseNotAndPictureUrlsIsNotNull(product_id, 0);
|
List<Comment> usedComments = commentRepository.findByProductIdAndIsUseNotAndPictureUrlsIsNotNull(product_id, 0);
|
||||||
|
|
||||||
canUseComentCount = availableComments.size();
|
canUseComentCount = availableComments.size();
|
||||||
usedCommentCount = usedComments.size();
|
usedCommentCount = usedComments.size();
|
||||||
|
allCommentCount = canUseComentCount + usedCommentCount;
|
||||||
|
|
||||||
Comment commentToUse = null;
|
Comment commentToUse = null;
|
||||||
|
|
||||||
|
// 1️⃣ 先尝试使用本地可用的京东评论
|
||||||
if (!availableComments.isEmpty()) {
|
if (!availableComments.isEmpty()) {
|
||||||
// 随机选取一个未使用的评论
|
|
||||||
Collections.shuffle(availableComments);
|
Collections.shuffle(availableComments);
|
||||||
commentToUse = availableComments.get(0);
|
commentToUse = availableComments.get(0);
|
||||||
} else {
|
} else {
|
||||||
wxUtil.sendTextMessage(fromWxid, "没有本地评论,调用外部接口抓取", 1, fromWxid, true);
|
|
||||||
// 没有本地评论,调用外部接口抓取
|
|
||||||
try {
|
|
||||||
String fetchUrl = "http://192.168.8.6:5000/fetch_comments?product_id=" + product_id;
|
|
||||||
// 用hutool发起post请求
|
|
||||||
HttpResponse response = HttpRequest.post(fetchUrl).timeout(1000 * 60).execute();
|
|
||||||
|
|
||||||
logger.info("fetchUrl: {}", fetchUrl);
|
/**
|
||||||
// code = 200 表示成功,-200 表示失败
|
* ✅ 新增逻辑:先尝试从淘宝获取评论,但前提是 productTypeMapTB 存在对应映射
|
||||||
if (response.getStatus() == 200) {
|
*/
|
||||||
wxUtil.sendTextMessage(fromWxid, "已获取新的评论,请稍等", 1, fromWxid, true);
|
getProductTypeMapForTB(); // 加载淘宝映射表
|
||||||
// ✅ 关键修改:重新从数据库中查询,而不是使用内存中的 fetchedComments
|
String taobaoProductId = productTypeMapTB.getOrDefault(product_id, null);
|
||||||
availableComments = commentRepository.findByProductIdAndIsUseNotAndPictureUrlsIsNotNull(product_id, 1);
|
|
||||||
if (!availableComments.isEmpty()) {
|
if (taobaoProductId != null && !taobaoProductId.isEmpty()) {
|
||||||
addCommentCount = availableComments.size() - canUseComentCount;
|
logger.info("发现淘宝映射ID,尝试从淘宝获取评论");
|
||||||
Collections.shuffle(availableComments);
|
wxUtil.sendTextMessage(fromWxid, "本地无可用京东评论,已发现淘宝映射ID,从淘宝获取评论", 1, null, true);
|
||||||
commentToUse = availableComments.get(0);
|
Comment taobaoComment = generateTaobaoComment(productType);
|
||||||
|
if (taobaoComment != null) {
|
||||||
|
commentToUse = taobaoComment;
|
||||||
|
isTb = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.info("未找到淘宝映射ID,继续使用京东评论流程");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 2️⃣ 然后尝试从京东外部接口获取新的评论
|
||||||
|
*/
|
||||||
|
if (!isTb) {
|
||||||
|
try {
|
||||||
|
String fetchUrl = "http://192.168.8.6:5000/fetch_comments?product_id=" + product_id;
|
||||||
|
HttpResponse response = HttpRequest.post(fetchUrl).timeout(1000 * 60).execute();
|
||||||
|
|
||||||
|
logger.info("fetchUrl: {}", fetchUrl);
|
||||||
|
|
||||||
|
if (response.getStatus() == 200) {
|
||||||
|
wxUtil.sendTextMessage(fromWxid, "已获取新的评论,请稍等", 1, fromWxid, true);
|
||||||
|
|
||||||
|
availableComments = commentRepository.findByProductIdAndIsUseNotAndPictureUrlsIsNotNull(product_id, 1);
|
||||||
|
if (!availableComments.isEmpty()) {
|
||||||
|
addCommentCount = availableComments.size() - canUseComentCount;
|
||||||
|
Collections.shuffle(availableComments);
|
||||||
|
commentToUse = availableComments.get(0);
|
||||||
|
}
|
||||||
|
} else if (response.getStatus() == -200) {
|
||||||
|
wxUtil.sendTextMessage(fromWxid, "请求响应:暂无新的评论。为您随机选取使用过的评论", 1, fromWxid, false);
|
||||||
|
if (!usedComments.isEmpty()) {
|
||||||
|
Collections.shuffle(usedComments);
|
||||||
|
commentToUse = usedComments.get(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (response.getStatus() == -200) {
|
|
||||||
wxUtil.sendTextMessage(fromWxid, "请求响应:暂无新的评论。为您随机选取使用过的评论", 1, fromWxid, false);
|
|
||||||
// 随机选取一个使用过的评论
|
|
||||||
Collections.shuffle(usedComments);
|
|
||||||
commentToUse = usedComments.get(0);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error("调用外部接口获取评论失败", e);
|
|
||||||
wxUtil.sendTextMessage(fromWxid, "请求响应:响应超时(60s)。为您随机选取使用过的评论", 1, fromWxid, false);
|
|
||||||
commentToUse = usedComments.get(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (commentToUse == null) {
|
} catch (Exception e) {
|
||||||
wxUtil.sendTextMessage(fromWxid, "本地和京东均无可用的评论数据,请检查sku", 1, fromWxid, false);
|
logger.error("调用外部接口获取评论失败", e);
|
||||||
return;
|
wxUtil.sendTextMessage(fromWxid, "请求响应:响应超时(60s)。为您随机选取使用过的评论", 1, fromWxid, false);
|
||||||
}
|
if (!usedComments.isEmpty()) {
|
||||||
wxUtil.sendTextMessage(fromWxid, commentToUse.getCommentText(), 1, fromWxid, true);
|
Collections.shuffle(usedComments);
|
||||||
// 发送图片(如果有)
|
commentToUse = usedComments.get(0);
|
||||||
String pictureUrls = commentToUse.getPictureUrls();
|
}
|
||||||
if (pictureUrls != null && !pictureUrls.isEmpty()) {
|
|
||||||
// 使用 fastjson 将 JSON 字符串解析为 List<String>
|
|
||||||
List<String> urlList = JSON.parseArray(pictureUrls, String.class);
|
|
||||||
|
|
||||||
for (String url : urlList) {
|
|
||||||
wxUtil.sendImageMessage(fromWxid, url.trim()); // 假设 sendImageMessage 支持 URL
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 调用 DeepSeek 生成新的评论内容
|
|
||||||
StringBuilder deepSeekPrompt = new StringBuilder(COMMENT_TEMPLATES_DS + commentToUse.getCommentText());
|
|
||||||
//String deepSeekResponse = "";
|
|
||||||
String gptResponse = "";
|
|
||||||
|
|
||||||
try {
|
|
||||||
//deepSeekResponse = deepSeekClientUtil.getDeepSeekResponse(deepSeekPrompt);
|
|
||||||
List<Comment> comments = commentRepository.findByProductIdAndPictureUrlsIsNotNull(product_id);
|
|
||||||
allCommentCount = comments.size();
|
|
||||||
// 随机截取至多10个
|
|
||||||
comments = comments.subList(0, Math.min(10, comments.size()));
|
|
||||||
for (Comment comment : comments) {
|
|
||||||
String commentText = comment.getCommentText();
|
|
||||||
if (commentText != null && !commentText.isEmpty()) {
|
|
||||||
deepSeekPrompt.append("\n").append(commentText);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
gptResponse = gptClientUtil.getGPTResponse(deepSeekPrompt.toString());
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error("生成评论异常 - 用户: {}", fromWxid, e);
|
|
||||||
wxUtil.sendTextMessage(fromWxid, "AI 评论生成失败", 1, fromWxid, false);
|
|
||||||
}
|
}
|
||||||
// 发送生成的评论文本
|
|
||||||
wxUtil.sendTextMessage(fromWxid, gptResponse, 1, fromWxid, true);
|
|
||||||
wxUtil.sendTextMessage(fromWxid, "评论统计:\n" + "型号 " + productType + "\n" + "新增:" + addCommentCount + "\n" + "已使用:" + usedCommentCount + "\n" + "可用:" + canUseComentCount + "\n" + "总数:" + allCommentCount, 1, fromWxid, true);
|
|
||||||
// 更新评论状态为已使用
|
|
||||||
commentToUse.setIsUse(1);
|
|
||||||
commentRepository.save(commentToUse);
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 🚀 如果京东本地和接口都没有,则再次尝试从淘宝评论获取(兜底逻辑)
|
||||||
|
*/
|
||||||
|
//if (commentToUse == null) {
|
||||||
|
// wxUtil.sendTextMessage(fromWxid, "京东无可用评论,尝试从淘宝评论中获取", 1, fromWxid, false);
|
||||||
|
// generateTaobaoComment(fromWxid, productType);
|
||||||
|
// return;
|
||||||
|
//}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ✨ 发送最终评论内容及图片
|
||||||
|
*/
|
||||||
|
if (commentToUse != null) {
|
||||||
|
wxUtil.sendTextMessage(fromWxid, commentToUse.getCommentText(), 1, fromWxid, true);
|
||||||
|
String pictureUrls = commentToUse.getPictureUrls();
|
||||||
|
if (pictureUrls != null && !pictureUrls.isEmpty()) {
|
||||||
|
List<String> urlList = JSON.parseArray(pictureUrls, String.class);
|
||||||
|
for (String url : urlList) {
|
||||||
|
wxUtil.sendImageMessage(fromWxid, url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 📝 更新评论状态为已使用(仅限数据库中的评论)
|
||||||
|
*/
|
||||||
|
if (commentToUse.getId() != null && !isTb) {
|
||||||
|
commentToUse.setIsUse(1);
|
||||||
|
commentRepository.save(commentToUse);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 📊 发送统计信息
|
||||||
|
*/
|
||||||
|
if (!isTb) {
|
||||||
|
wxUtil.sendTextMessage(fromWxid,
|
||||||
|
"京东评论统计:\n" +
|
||||||
|
"型号 " + productType + "\n" +
|
||||||
|
"新增:" + addCommentCount + "\n" +
|
||||||
|
"已使用:" + usedCommentCount + "\n" +
|
||||||
|
"可用:" + canUseComentCount + "\n" +
|
||||||
|
"总数:" + allCommentCount, 1, fromWxid, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Comment generateTaobaoComment(String productType) {
|
||||||
|
getProductTypeMap(); // 加载京东的 productTypeMap
|
||||||
|
getProductTypeMapForTB(); // 加载淘宝的 productTypeMapTB
|
||||||
|
|
||||||
|
String product_id = productTypeMap.get(productType); // 先查京东SKU
|
||||||
|
|
||||||
|
if (product_id == null) {
|
||||||
|
logger.info("未找到对应的京东商品ID:{}", productType);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ 在这里进行淘宝的 product_id 映射转换
|
||||||
|
String taobaoProductId = productTypeMapTB.getOrDefault(product_id, product_id);
|
||||||
|
|
||||||
|
// 然后使用 taobaoProductId 去查询 TaobaoComment
|
||||||
|
List<TaobaoComment> taobaoComments = taobaoCommentRepository.findByProductIdAndIsUseNotAndPictureUrlsIsNotNull(taobaoProductId, 1);
|
||||||
|
logger.info("taobaoComments.size() {}", taobaoComments.size());
|
||||||
|
if (!taobaoComments.isEmpty()) {
|
||||||
|
Collections.shuffle(taobaoComments);
|
||||||
|
TaobaoComment selected = taobaoComments.get(0);
|
||||||
|
// 将淘宝评论转换为京东评论返回
|
||||||
|
Comment comment = new Comment();
|
||||||
|
comment.setCommentText(selected.getCommentText());
|
||||||
|
String pictureUrls = selected.getPictureUrls();
|
||||||
|
if (pictureUrls != null) {
|
||||||
|
pictureUrls = pictureUrls.replace("//img.", "https://img.");
|
||||||
|
}
|
||||||
|
comment.setPictureUrls(pictureUrls);
|
||||||
|
comment.setCommentId(selected.getCommentId());
|
||||||
|
comment.setProductId(product_id);
|
||||||
|
comment.setUserName(selected.getUserName());
|
||||||
|
comment.setCreatedAt(selected.getCreatedAt());
|
||||||
|
// 保存淘宝评论为已使用
|
||||||
|
selected.setIsUse(1);
|
||||||
|
taobaoCommentRepository.save(selected);
|
||||||
|
|
||||||
|
// 返回京东评论
|
||||||
|
return comment;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,106 +0,0 @@
|
|||||||
package cn.van.business.util;
|
|
||||||
|
|
||||||
import io.jsonwebtoken.Jwts;
|
|
||||||
import io.jsonwebtoken.SignatureAlgorithm;
|
|
||||||
import io.jsonwebtoken.security.Keys;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JWT 工具类
|
|
||||||
* 提供 token 的生成、解析、验证和有效期管理功能
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
public class JwtUtils {
|
|
||||||
|
|
||||||
private final String secret = "7b4c86d11fe2b64a5ce4d7d83de881b4afa76bf4a8bbc13ee50676c20b14c0835dda8a78a5a795b0f2201a26f758301b56ffbc52d021e158f1365d8be22fa6d7"; // 应配置在 application.yml 中
|
|
||||||
private final long expiration = 86400000 * 7; // 24小时
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 生成一个新的 JWT Token
|
|
||||||
* @param username 用户名
|
|
||||||
* @param roles 用户角色(可选)
|
|
||||||
* @return 返回生成的 JWT Token 字符串
|
|
||||||
*/
|
|
||||||
// 生成 Token 时去掉 roles 参数
|
|
||||||
public String generateToken(String username) {
|
|
||||||
return Jwts.builder()
|
|
||||||
.setSubject(username)
|
|
||||||
.setExpiration(new Date(System.currentTimeMillis() + expiration))
|
|
||||||
.signWith(SignatureAlgorithm.HS512, secret)
|
|
||||||
.compact();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从给定的 JWT Token 中提取用户名
|
|
||||||
*
|
|
||||||
* @param token JWT Token
|
|
||||||
* @return 提取的用户名
|
|
||||||
*/
|
|
||||||
public String extractUsername(String token) {
|
|
||||||
return Jwts.parser()
|
|
||||||
.setSigningKey(secret)
|
|
||||||
.parseClaimsJws(token)
|
|
||||||
.getBody()
|
|
||||||
.getSubject();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取 JWT Token 的过期时间
|
|
||||||
*
|
|
||||||
* @param token JWT Token
|
|
||||||
* @return 返回 token 的过期时间 (Date)
|
|
||||||
*/
|
|
||||||
public Date extractExpiration(String token) {
|
|
||||||
return Jwts.parser()
|
|
||||||
.setSigningKey(secret)
|
|
||||||
.parseClaimsJws(token)
|
|
||||||
.getBody()
|
|
||||||
.getExpiration();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 验证指定的 JWT Token 是否有效
|
|
||||||
*
|
|
||||||
* @param token JWT Token
|
|
||||||
* @return 如果 token 有效则返回 true,否则返回 false
|
|
||||||
*/
|
|
||||||
public Boolean validateToken(String token) {
|
|
||||||
return !isTokenExpired(token);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查 token 是否已过期
|
|
||||||
*
|
|
||||||
* @param token JWT Token
|
|
||||||
* @return 如果已过期返回 true,否则返回 false
|
|
||||||
*/
|
|
||||||
private Boolean isTokenExpired(String token) {
|
|
||||||
return extractExpiration(token).before(new Date());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取 JWT Token 的剩余有效时间(毫秒)
|
|
||||||
*
|
|
||||||
* @param token JWT Token
|
|
||||||
* @return 剩余的有效时间(毫秒)
|
|
||||||
*/
|
|
||||||
public long getRemainingExpirationTime(String token) {
|
|
||||||
long now = System.currentTimeMillis();
|
|
||||||
return Math.max(0, extractExpiration(token).getTime() - now);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取 JWT Token 的过期时间(毫秒)
|
|
||||||
*
|
|
||||||
* @param token JWT Token
|
|
||||||
* @return 返回 token 的过期时间戳(毫秒)
|
|
||||||
*/
|
|
||||||
public long getExpiration(String token) {
|
|
||||||
return extractExpiration(token).getTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
80
src/main/java/cn/van/business/util/OtherUtil.java
Normal file
80
src/main/java/cn/van/business/util/OtherUtil.java
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
package cn.van.business.util;
|
||||||
|
|
||||||
|
import cn.hutool.http.HttpRequest;
|
||||||
|
import com.alibaba.fastjson2.JSON;
|
||||||
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Leo
|
||||||
|
* @version 1.0
|
||||||
|
* @create 2025/7/8 21:13
|
||||||
|
* @description:
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class OtherUtil {
|
||||||
|
final WXUtil wxUtil;
|
||||||
|
|
||||||
|
public OtherUtil(WXUtil wxUtil) {
|
||||||
|
this.wxUtil = wxUtil;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void tmyk(String msg, String fromWxid) {
|
||||||
|
// 正则表达式:匹配pagepath标签中的tid参数值
|
||||||
|
// 解释:
|
||||||
|
// <pagepath><!\[CDATA\[ 匹配pagepath的CDATA开始标签
|
||||||
|
// [^\]]*?tid= 非贪婪匹配任意字符,直到遇到tid=
|
||||||
|
// (\d+) 捕获一个或多个数字(tid的值)
|
||||||
|
// \]\]> 匹配CDATA结束标签
|
||||||
|
String regex = "<pagepath><!\\[CDATA\\[[^\\]]*?tid=(\\d+)\\]\\]></pagepath>";
|
||||||
|
|
||||||
|
// 编译正则表达式,忽略大小写(防止标签大小写不一致)
|
||||||
|
Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
|
||||||
|
Matcher matcher = pattern.matcher(msg);
|
||||||
|
|
||||||
|
// 查找匹配项
|
||||||
|
if (matcher.find()) {
|
||||||
|
// 返回第一个捕获组(即tid的值)
|
||||||
|
String tid = matcher.group(1);
|
||||||
|
// 构建post请求 url:https://tm.wx.hackp.net/App/zm/getlist,参数:tid=tid放在body
|
||||||
|
String url = "https://tm.wx.hackp.net/App/zm/getlist";
|
||||||
|
String response = HttpRequest.post(url)
|
||||||
|
.body("tid=" + tid)
|
||||||
|
.execute().body();
|
||||||
|
if (Util.isNotEmpty(response)) {
|
||||||
|
JSONObject jsonObject = JSON.parseObject(response);
|
||||||
|
/**
|
||||||
|
* {
|
||||||
|
* "code": "1",
|
||||||
|
* "msg": {
|
||||||
|
* "id": "1987",
|
||||||
|
* "type": "3",
|
||||||
|
* "tid": "52",
|
||||||
|
* "hot": "2",
|
||||||
|
* "title": "头条号项目玩法,从账号注册,素材获取到视频制作发布和裂变全方位教学 ",
|
||||||
|
* "picname": "https://tm.wx.hackp.net/data/attachment/temp/202111/07/130031qb5lw95b259ehlnn.jpg_thumb.jpg",
|
||||||
|
* "content": "<span style=\"color:#555555;font-family:"font-size:large;background-color:#FFFFFF;\">1.项目玩法介绍mp4</span><br />\r\n<br />\r\n<span style=\"color:#555555;font-family:"font-size:large;background-color:#FFFFFF;\">2.账号注册.mp4</span><br />\r\n<br />\r\n<span style=\"color:#555555;font-family:"font-size:large;background-color:#FFFFFF;\">3.素材获取mp4</span><br />\r\n<br />\r\n<span style=\"color:#555555;font-family:"font-size:large;background-color:#FFFFFF;\">4.视频批星制作.mp4</span><br />\r\n<br />\r\n<span style=\"color:#555555;font-family:"font-size:large;background-color:#FFFFFF;\">5.视频发布.mp4</span><br />\r\n<br />\r\n<span style=\"color:#555555;font-family:"font-size:large;background-color:#FFFFFF;\">6.视频裂变.mp4</span>",
|
||||||
|
* "count": "10122",
|
||||||
|
* "dizhi": "下载地址:链接: https://pan.baidu.com/s/104YEJVgTx4ZSNVJjw7n9oQ提取码: 92kr",
|
||||||
|
* "price": "0",
|
||||||
|
* "addtime": "1636261305",
|
||||||
|
* "status": "1",
|
||||||
|
* "hackp_id": "36657",
|
||||||
|
* "buygoods": null
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* */
|
||||||
|
JSONObject msgResponse = jsonObject.getJSONObject("msg");
|
||||||
|
// title和dizhi
|
||||||
|
wxUtil.sendTextMessage(fromWxid, msgResponse.getString("title") + "\n" + msgResponse.getString("dizhi"), 0, null, true);
|
||||||
|
} else {
|
||||||
|
wxUtil.sendTextMessage(fromWxid, "请求失败", 0, null, true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
wxUtil.sendTextMessage(fromWxid, "没有匹配到tid", 0, null, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -199,6 +199,10 @@ public class WXUtil {
|
|||||||
jdidToWxidMap.put(admin13.getUnionId(), admin13.getWxid());
|
jdidToWxidMap.put(admin13.getUnionId(), admin13.getWxid());
|
||||||
jdidToRemarkMap.put(admin13.getUnionId(), admin13.getName());
|
jdidToRemarkMap.put(admin13.getUnionId(), admin13.getName());
|
||||||
|
|
||||||
|
/**/
|
||||||
|
SuperAdmin admin14 = new SuperAdmin("wxid_m5ibcpe0ukw521", "小怪兽", "", "", "");
|
||||||
|
super_admins.put(admin14.getWxid() + admin14.getUnionId(), admin14);
|
||||||
|
|
||||||
|
|
||||||
/* 内部管理群 */
|
/* 内部管理群 */
|
||||||
|
|
||||||
|
|||||||
@@ -26,9 +26,12 @@ public class WxMessageConsumer {
|
|||||||
|
|
||||||
private final JDUtil jdUtils;
|
private final JDUtil jdUtils;
|
||||||
|
|
||||||
|
private final OtherUtil otherUtil;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public WxMessageConsumer(@Lazy JDUtil jdUtils) {
|
public WxMessageConsumer(@Lazy JDUtil jdUtils, OtherUtil otherUtil) {
|
||||||
this.jdUtils = jdUtils;
|
this.jdUtils = jdUtils;
|
||||||
|
this.otherUtil = otherUtil;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Async("threadPoolTaskExecutor")
|
@Async("threadPoolTaskExecutor")
|
||||||
@@ -49,7 +52,7 @@ public class WxMessageConsumer {
|
|||||||
*
|
*
|
||||||
* @param wxMessage
|
* @param wxMessage
|
||||||
*/
|
*/
|
||||||
private void handlePrivateMessage(WxMessage wxMessage) throws Exception {
|
private void handlePrivateMessage(WxMessage wxMessage) {
|
||||||
logger.info("处理私聊消息: {}", JSON.toJSONString(wxMessage));
|
logger.info("处理私聊消息: {}", JSON.toJSONString(wxMessage));
|
||||||
|
|
||||||
WxMessage.DataSection data = wxMessage.getData();
|
WxMessage.DataSection data = wxMessage.getData();
|
||||||
@@ -77,6 +80,10 @@ public class WxMessageConsumer {
|
|||||||
jdUtils.sendOrderToWxByOrderD(msg.replace("单", ""), fromWxid);
|
jdUtils.sendOrderToWxByOrderD(msg.replace("单", ""), fromWxid);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (msg.contains("<sourcedisplayname>唐门云课</sourcedisplayname>")){
|
||||||
|
logger.info("消息来自唐门云课,处理指令消息" );
|
||||||
|
otherUtil.tmyk(msg,fromWxid);
|
||||||
|
}
|
||||||
logger.info("未命中前置指令,开始命中 Default 流程");
|
logger.info("未命中前置指令,开始命中 Default 流程");
|
||||||
jdUtils.sendOrderToWxByOrderDefault(msg, fromWxid);
|
jdUtils.sendOrderToWxByOrderDefault(msg, fromWxid);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user