diff --git a/pjcloud-common/pjcloud-common-core/src/main/java/com/bsd/pjcloud/common/core/constant/CacheConstants.java b/pjcloud-common/pjcloud-common-core/src/main/java/com/bsd/pjcloud/common/core/constant/CacheConstants.java index 7756812ef..ae4eecda3 100644 --- a/pjcloud-common/pjcloud-common-core/src/main/java/com/bsd/pjcloud/common/core/constant/CacheConstants.java +++ b/pjcloud-common/pjcloud-common-core/src/main/java/com/bsd/pjcloud/common/core/constant/CacheConstants.java @@ -20,7 +20,7 @@ public interface CacheConstants { */ String DEFAULT_CODE_KEY = "DEFAULT_CODE_KEY:"; - String VISITOR_CODE_KEY = "visitor_code_key"; + String VISITOR_CODE_KEY = "visitor_code_key:"; String SMS_CODE_KEY = "smsCode_"; diff --git a/pjcloud-integrated/pjcloud-integrated-biz/pom.xml b/pjcloud-integrated/pjcloud-integrated-biz/pom.xml index e45b58ff1..3eefd3f44 100644 --- a/pjcloud-integrated/pjcloud-integrated-biz/pom.xml +++ b/pjcloud-integrated/pjcloud-integrated-biz/pom.xml @@ -14,11 +14,16 @@ + + + + + - io.jsonwebtoken - jjwt - 0.9.1 + cn.hutool + hutool-jwt + com.bsd diff --git a/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/config/VisitorTokenInterceptor.java b/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/config/VisitorTokenInterceptor.java new file mode 100644 index 000000000..057fe2e50 --- /dev/null +++ b/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/config/VisitorTokenInterceptor.java @@ -0,0 +1,71 @@ +package com.bsd.pjcloud.integrated.config; + +import cn.hutool.jwt.JWT; +import cn.hutool.jwt.JWTUtil; +import com.bsd.pjcloud.integrated.service.VisitorsService; +import com.bsd.pjcloud.integrated.service.VisitorUserService; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; + +@Component +public class VisitorTokenInterceptor implements HandlerInterceptor { + @Value("${visitor.jwt-secret}") + private String secret; + + private final VisitorUserService visitorUserService; + + public VisitorTokenInterceptor(@Lazy VisitorUserService visitorUserService) { + this.visitorUserService = visitorUserService; + } + + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + String token = request.getHeader("token"); + if (token == null || token.isEmpty()) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.getWriter().write("Unauthorized: token missing"); + return false; + } + JWT jwt = JWTUtil.parseToken(token); + boolean verify = jwt.setKey(secret.getBytes()).verify(); + if (!verify) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.getWriter().write("Unauthorized: token invalid"); + return false; + } + // 校验过期时间 + Object expObj = jwt.getPayload("exp"); + if (expObj == null || Long.parseLong(expObj.toString()) < System.currentTimeMillis()) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.getWriter().write("Unauthorized: token expired"); + return false; + } + Object userType = jwt.getPayload("userType"); + if (userType == null || !"visitor".equals(userType.toString())) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.getWriter().write("Unauthorized: not visitor"); + return false; + } + // 账号状态校验 + Object idObj = jwt.getPayload("id"); + if (idObj == null) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.getWriter().write("Unauthorized: no user id"); + return false; + } + Long userId = Long.valueOf(idObj.toString()); + com.bsd.pjcloud.integrated.entity.VisitorUserEntity user = visitorUserService.getById(userId); + if (user == null || "1".equals(user.getStatus())) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.getWriter().write("Unauthorized: account locked or not exist"); + return false; + } + return true; + } +} \ No newline at end of file diff --git a/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/config/WebMvcConfig.java b/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/config/WebMvcConfig.java new file mode 100644 index 000000000..cba666ac0 --- /dev/null +++ b/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/config/WebMvcConfig.java @@ -0,0 +1,17 @@ +package com.bsd.pjcloud.integrated.config; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +@RequiredArgsConstructor +public class WebMvcConfig implements WebMvcConfigurer { + private final VisitorTokenInterceptor visitorTokenInterceptor; + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(visitorTokenInterceptor) + .addPathPatterns("/app/visitor/**"); + } +} \ No newline at end of file diff --git a/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/controller/AppVisitorController.java b/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/controller/AppVisitorController.java new file mode 100644 index 000000000..d3751ce80 --- /dev/null +++ b/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/controller/AppVisitorController.java @@ -0,0 +1,59 @@ +package com.bsd.pjcloud.integrated.controller; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.jwt.JWT; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.bsd.pjcloud.common.core.constant.CacheConstants; +import com.bsd.pjcloud.common.core.util.R; +import com.bsd.pjcloud.common.security.annotation.Inner; +import com.bsd.pjcloud.integrated.entity.CarInfoEntity; +import com.bsd.pjcloud.integrated.entity.VisitorUserEntity; +import com.bsd.pjcloud.integrated.service.VisitorUserService; +import com.bsd.pjcloud.integrated.service.VisitorsService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springdoc.core.annotations.ParameterObject; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.http.HttpHeaders; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.*; + +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.Map; + +/** + * 访客用户 + * + * @author zjf + * @date 2025-07-17 14:58:26 + */ +@RestController +@RequiredArgsConstructor +@RequestMapping("/app/visitor" ) +@Tag(description = "visitorUser" , name = "访客用户管理" ) +@SecurityRequirement(name = HttpHeaders.AUTHORIZATION) +@Inner(value = false) +public class AppVisitorController { + + private final VisitorUserService visitorUserService; + @Autowired + private RedisTemplate redisTemplate; + + private final VisitorsService visitorsService; + + @Operation(summary = "分页查询" , description = "分页查询" ) + @GetMapping("/page" ) + public R getCarInfoPage(@ParameterObject Page page, @ParameterObject CarInfoEntity carInfo) { + + return R.ok(visitorsService.list()); + } + + +} \ No newline at end of file diff --git a/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/controller/AppVisitorUserController.java b/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/controller/AppVisitorUserController.java index 1f746699b..cd2de7856 100644 --- a/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/controller/AppVisitorUserController.java +++ b/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/controller/AppVisitorUserController.java @@ -3,6 +3,7 @@ package com.bsd.pjcloud.integrated.controller; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; +import cn.hutool.jwt.JWT; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; @@ -14,7 +15,6 @@ import com.bsd.pjcloud.common.log.annotation.SysLog; import com.bsd.pjcloud.common.security.annotation.Inner; import com.bsd.pjcloud.integrated.entity.VisitorUserEntity; import com.bsd.pjcloud.integrated.service.VisitorUserService; -import io.jsonwebtoken.Jwts; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; @@ -24,10 +24,12 @@ import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import org.springdoc.core.annotations.ParameterObject; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.http.HttpHeaders; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.*; import java.time.LocalDateTime; @@ -45,39 +47,52 @@ import java.util.concurrent.TimeUnit; @RequestMapping("/app/visitorUser" ) @Tag(description = "visitorUser" , name = "访客用户管理" ) @SecurityRequirement(name = HttpHeaders.AUTHORIZATION) +@Inner(value = false) public class AppVisitorUserController { private final VisitorUserService visitorUserService; @Autowired private RedisTemplate redisTemplate; + @Value("${visitor.jwt-secret}") + private String secret; + // 登录验证 @PostMapping("/login") +// @Inner(value = false) + @Transactional public R> login( @RequestParam String phone, @RequestParam String smsCode, @RequestParam String randomStr, + @RequestParam String imageCode, @RequestParam(value = "realName", required = false) String realName ) { Map response = new HashMap<>(); + //0. 验证图片验证码 + if (!verifyImageCode(randomStr, imageCode)) { + return R.failed("图片验证码错误或已过期"); + } + // 1. 验证短信验证码 - if (verifySmsCode(phone,randomStr, smsCode)) { + if (!verifySmsCode(phone, smsCode)) { return R.failed("短信验证码错误或已过期"); } // 2. 查找或创建账户 VisitorUserEntity account = visitorUserService.getOne(new LambdaQueryWrapper().eq(VisitorUserEntity::getPhone, phone)); - account.setLastLogin(LocalDateTime.now()); if (account == null){ account = new VisitorUserEntity(); account.setPhone(phone); account.setRealName(realName); account.setStatus("0"); account.setRegisterTime(LocalDateTime.now()); + account.setLastLogin(LocalDateTime.now()); visitorUserService.save(account); }else { + account.setLastLogin(LocalDateTime.now()); // 3. 检查账户状态 if (account.getStatus().equals("1")) { response.put("code", 4006); @@ -87,16 +102,14 @@ public class AppVisitorUserController { visitorUserService.updateById( account); } - Map stringStringHashMap = new HashMap<>(); - stringStringHashMap.put("phone", phone); - stringStringHashMap.put("realName", realName); - stringStringHashMap.put("randomStr", randomStr); - stringStringHashMap.put("smsCode", smsCode); - String token=Jwts.builder().setSubject(account.getId().toString()) - .setIssuedAt(new Date()) - .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 24 * 7)) - .setClaims(stringStringHashMap).compact(); - + String token = cn.hutool.jwt.JWT.create() + .setPayload("id", account.getId()) + .setPayload("phone", account.getPhone()) + .setPayload("userType", "visitor") + .setPayload("realName", account.getRealName()) + .setPayload("exp", System.currentTimeMillis() + 1000 * 60 * 60 * 24 * 7) + .setKey(secret.getBytes()) + .sign(); response.put("code", 200); response.put("message", "登录成功"); @@ -105,21 +118,25 @@ public class AppVisitorUserController { return R.ok(response); } - private boolean verifySmsCode(String phone, String randomStr, String smsCode) { - String key = CacheConstants.SMS_VISITOR_CODE_KEY + phone + "_" + randomStr; - return !redisTemplate.opsForValue().get(key).equals(smsCode); - } - - - private String getClientIp(HttpServletRequest request) { - String ip = request.getHeader("X-Forwarded-For"); - if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { - ip = request.getRemoteAddr(); + private boolean verifyImageCode(String randomStr, String imageCode) { + String key = CacheConstants.VISITOR_CODE_KEY + randomStr; + Object imageCodeValueObj = redisTemplate.opsForValue().get(key); + if (imageCodeValueObj == null) { + return false; } - return ip; + String imageCodeValue = imageCodeValueObj.toString(); + return imageCodeValue.equals(imageCode); } - + private boolean verifySmsCode(String phone, String smsCode) { + String key = CacheConstants.SMS_VISITOR_CODE_KEY + phone; + Object smsCodeValueObj = redisTemplate.opsForValue().get(key); + if (smsCodeValueObj == null) { + return false; + } + String o = smsCodeValueObj.toString(); + return o.equals(smsCode); + } } \ No newline at end of file diff --git a/pjcloud-integrated/pjcloud-integrated-biz/src/main/resources/application.yml b/pjcloud-integrated/pjcloud-integrated-biz/src/main/resources/application.yml index 3da70eeae..fa8b191d0 100644 --- a/pjcloud-integrated/pjcloud-integrated-biz/src/main/resources/application.yml +++ b/pjcloud-integrated/pjcloud-integrated-biz/src/main/resources/application.yml @@ -23,3 +23,5 @@ spring: # username: root # password: root # url: jdbc:mysql://pjcloud-mysql:3306/pjcloudx_integrated?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8&allowMultiQueries=true +visitor: + jwt-secret: "u8Jk2n3Lk4pQ5r6T7s8V9w0X1y2Z3a4B"