Sfoglia il codice sorgente

refactor: 第一次提交

yy 6 giorni fa
parent
commit
b6709ade5d
40 ha cambiato i file con 1770 aggiunte e 1 eliminazioni
  1. 2 1
      jeecg-boot-base-core/src/main/java/org/jeecg/common/aspect/DictAspect.java
  2. 11 0
      jeecg-boot-base-core/src/main/java/org/jeecg/common/aspect/annotation/EnableDictTranslate.java
  3. 19 0
      jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/PartnerUserLoginUser.java
  4. 61 0
      jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/PartnerShiroRealm.java
  5. 28 0
      jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/PartnerToken.java
  6. 69 0
      jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/util/ShopTokenUtil.java
  7. 19 0
      jeecg-boot-base-core/src/main/java/org/jeecg/config/vo/CustomerLoginUser.java
  8. 37 0
      jeecg-module-system/jeecg-shop/pom.xml
  9. 37 0
      jeecg-module-system/jeecg-shop/src/main/java/org/jeecg/modules/shop/captcha/config/EmailConfig.java
  10. 27 0
      jeecg-module-system/jeecg-shop/src/main/java/org/jeecg/modules/shop/captcha/config/SmsClientConfig.java
  11. 48 0
      jeecg-module-system/jeecg-shop/src/main/java/org/jeecg/modules/shop/captcha/controller/CaptchaController.java
  12. 28 0
      jeecg-module-system/jeecg-shop/src/main/java/org/jeecg/modules/shop/captcha/entity/SendSmsCaptchaDTO.java
  13. 30 0
      jeecg-module-system/jeecg-shop/src/main/java/org/jeecg/modules/shop/captcha/entity/VerifySmsCaptchaDTO.java
  14. 57 0
      jeecg-module-system/jeecg-shop/src/main/java/org/jeecg/modules/shop/captcha/enums/CaptchaKeyEnum.java
  15. 28 0
      jeecg-module-system/jeecg-shop/src/main/java/org/jeecg/modules/shop/captcha/enums/CaptchaTypeEnum.java
  16. 21 0
      jeecg-module-system/jeecg-shop/src/main/java/org/jeecg/modules/shop/captcha/service/ICaptchaService.java
  17. 154 0
      jeecg-module-system/jeecg-shop/src/main/java/org/jeecg/modules/shop/captcha/service/impl/CaptchaServiceImpl.java
  18. 142 0
      jeecg-module-system/jeecg-shop/src/main/java/org/jeecg/modules/shop/captcha/util/EmailUtil.java
  19. 56 0
      jeecg-module-system/jeecg-shop/src/main/java/org/jeecg/modules/shop/captcha/util/SmsUtil.java
  20. 91 0
      jeecg-module-system/jeecg-shop/src/main/java/org/jeecg/modules/shop/user/controller/PartnerUserController.java
  21. 170 0
      jeecg-module-system/jeecg-shop/src/main/java/org/jeecg/modules/shop/user/entity/PartnerUser.java
  22. 82 0
      jeecg-module-system/jeecg-shop/src/main/java/org/jeecg/modules/shop/user/entity/dto/UserInfoDTO.java
  23. 26 0
      jeecg-module-system/jeecg-shop/src/main/java/org/jeecg/modules/shop/user/entity/dto/UserLoginDTO.java
  24. 96 0
      jeecg-module-system/jeecg-shop/src/main/java/org/jeecg/modules/shop/user/entity/vo/UserInfoVO.java
  25. 21 0
      jeecg-module-system/jeecg-shop/src/main/java/org/jeecg/modules/shop/user/entity/vo/UserLoginVO.java
  26. 18 0
      jeecg-module-system/jeecg-shop/src/main/java/org/jeecg/modules/shop/user/mapper/PartnerUserMapper.java
  27. 5 0
      jeecg-module-system/jeecg-shop/src/main/java/org/jeecg/modules/shop/user/mapper/xml/PartnerUserMapper.xml
  28. 25 0
      jeecg-module-system/jeecg-shop/src/main/java/org/jeecg/modules/shop/user/service/IPartnerUserService.java
  29. 127 0
      jeecg-module-system/jeecg-shop/src/main/java/org/jeecg/modules/shop/user/service/impl/PartnerUserServiceImpl.java
  30. 51 0
      jeecg-module-system/jeecg-shop/src/main/java/org/jeecg/modules/shop/user/util/CustomerUserHolder.java
  31. 5 0
      jeecg-module-system/jeecg-system-biz/pom.xml
  32. 31 0
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/sequence/entity/MiddleSequence.java
  33. 14 0
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/sequence/mapper/MiddleSequenceMapper.java
  34. 5 0
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/sequence/mapper/xml/MiddleSequenceMapper.xml
  35. 24 0
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/sequence/service/IMiddleSequenceService.java
  36. 53 0
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/sequence/service/impl/MiddleSequenceServiceImpl.java
  37. 5 0
      jeecg-module-system/jeecg-system-start/pom.xml
  38. 23 0
      jeecg-module-system/jeecg-system-start/src/main/resources/application-dev.yml
  39. 23 0
      jeecg-module-system/jeecg-system-start/src/main/resources/application-prod.yml
  40. 1 0
      jeecg-module-system/pom.xml

+ 2 - 1
jeecg-boot-base-core/src/main/java/org/jeecg/common/aspect/DictAspect.java

@@ -14,6 +14,7 @@ import org.aspectj.lang.annotation.Pointcut;
 import org.jeecg.common.api.CommonAPI;
 import org.jeecg.common.api.vo.Result;
 import org.jeecg.common.aspect.annotation.Dict;
+import org.jeecg.common.aspect.annotation.EnableDictTranslate;
 import org.jeecg.common.constant.CommonConstant;
 import org.jeecg.common.system.vo.DictModel;
 import org.jeecg.common.util.oConvertUtils;
@@ -213,7 +214,7 @@ public class DictAspect {
 //                ((IPage) ((Result) result).getResult()).setRecords(items);
             }
 
-            if (((Result<?>) result).getResult() != null && ((Result<?>) result).getResult().getClass().getDeclaredAnnotation(TableName.class) != null) {
+            if (((Result<?>) result).getResult() != null && (((Result<?>) result).getResult().getClass().getDeclaredAnnotation(TableName.class) != null || ((Result<?>) result).getResult().getClass().getDeclaredAnnotation(EnableDictTranslate.class) != null)) {
                 Object obj = ((Result<?>) result).getResult();
                 log.debug(" ---- 实体类进行字典映射");
                 // step.1 筛选出加了 Dict 注解的字段列表

+ 11 - 0
jeecg-boot-base-core/src/main/java/org/jeecg/common/aspect/annotation/EnableDictTranslate.java

@@ -0,0 +1,11 @@
+package org.jeecg.common.aspect.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface EnableDictTranslate {
+}

+ 19 - 0
jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/PartnerUserLoginUser.java

@@ -0,0 +1,19 @@
+package org.jeecg.common.system.vo;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+import org.jeecg.common.desensitization.annotation.SensitiveField;
+
+@Data
+@Accessors(chain = true)
+@EqualsAndHashCode(callSuper = false)
+public class PartnerUserLoginUser {
+    
+    /**
+     * 客户用户ID
+     */
+    @SensitiveField
+    private String id;
+    
+}

+ 61 - 0
jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/PartnerShiroRealm.java

@@ -0,0 +1,61 @@
+package org.jeecg.config.shiro;
+
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authc.SimpleAuthenticationInfo;
+import org.apache.shiro.authz.AuthorizationInfo;
+import org.apache.shiro.realm.AuthorizingRealm;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.jeecg.common.system.vo.PartnerUserLoginUser;
+import org.jeecg.config.shiro.util.ShopTokenUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Component;
+
+@Component
+public class PartnerShiroRealm extends AuthorizingRealm {
+    
+    @Autowired
+    private ShopTokenUtil shopTokenUtil;
+    
+    @Autowired
+    private RedisTemplate<String, String> redisTemplate;
+
+    @Override
+    public boolean supports(AuthenticationToken token) {
+        return token instanceof PartnerToken;
+    }
+
+    @Override
+    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
+        return null;
+    }
+
+    @Override
+    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
+        String token = (String) authenticationToken.getCredentials();
+        boolean verified = shopTokenUtil.verifyPartnerCustomerUserToken(token);
+        if (!verified) {
+            return null;
+        }
+        Boolean hasKey = redisTemplate.hasKey("shop:user:invalid-token:" + token);
+        if (Boolean.TRUE.equals(hasKey)) {
+            return null;
+        }
+        PartnerUserLoginUser customerLoginUser = new PartnerUserLoginUser();
+        customerLoginUser.setId(shopTokenUtil.getUid(token));
+        return new SimpleAuthenticationInfo(customerLoginUser, token, getName());
+    }
+
+    @Override
+    public void clearCache(PrincipalCollection principals) {
+        super.clearCache(principals);
+    }
+
+    @Override
+    public boolean isAuthorizationCachingEnabled() {
+        return false;
+    }
+
+}

+ 28 - 0
jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/PartnerToken.java

@@ -0,0 +1,28 @@
+package org.jeecg.config.shiro;
+ 
+import org.apache.shiro.authc.AuthenticationToken;
+
+/**
+ * @Author Scott
+ * @create 2018-07-12 15:19
+ * @desc
+ **/
+public class PartnerToken implements AuthenticationToken {
+
+	private static final long serialVersionUID = 1L;
+	private String token;
+
+    public PartnerToken(String token) {
+        this.token = token;
+    }
+ 
+    @Override
+    public Object getPrincipal() {
+        return token;
+    }
+ 
+    @Override
+    public Object getCredentials() {
+        return token;
+    }
+}

+ 69 - 0
jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/util/ShopTokenUtil.java

@@ -0,0 +1,69 @@
+package org.jeecg.config.shiro.util;
+
+import cn.hutool.core.date.DateUnit;
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.util.StrUtil;
+import com.auth0.jwt.JWT;
+import com.auth0.jwt.JWTVerifier;
+import com.auth0.jwt.algorithms.Algorithm;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import java.util.Calendar;
+import java.util.Date;
+
+@Slf4j
+@Component
+public class ShopTokenUtil {
+
+
+    @Value("${shop.user.partner.token-key}")
+    private String partnerTokenKey;
+    @Value("${shop.user.partner.token-expire}")
+    private int partnerTokenExpire;
+
+
+
+
+    public String createPartnerCustomerUserToken(String uid) {
+        Date now = new Date();
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTime(now);
+        calendar.add(Calendar.HOUR, partnerTokenExpire);
+        Date exp = calendar.getTime();
+        return JWT.create().withAudience(uid)
+                .withIssuedAt(now)
+                .withExpiresAt(exp)
+                .withClaim("uid", uid)
+                .sign(Algorithm.HMAC256(partnerTokenKey));
+    }
+
+    public boolean verifyPartnerCustomerUserToken(String token) {
+        try {
+            if (StrUtil.isBlank(token)) {
+                return false;
+            }
+            JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(partnerTokenKey)).build();
+            jwtVerifier.verify(token);
+            return true;
+        } catch (Exception e) {
+            log.warn("CustomerUserToken验证失败,token={},message={}", token, e.getMessage());
+            return false;
+        }
+    }
+    
+    public String getUid(String token) {
+        // token 必须是有效的
+        return JWT.decode(token).getClaim("uid").asString();
+    }
+    
+    
+    public long getRemainingSeconds(String token) {
+        // token 必须是有效的
+        Date issuedAt = JWT.decode(token).getIssuedAt();
+        Date expiresAt = JWT.decode(token).getExpiresAt();
+        return DateUtil.between(issuedAt, expiresAt, DateUnit.SECOND, true);
+    }
+    
+}

+ 19 - 0
jeecg-boot-base-core/src/main/java/org/jeecg/config/vo/CustomerLoginUser.java

@@ -0,0 +1,19 @@
+package org.jeecg.config.vo;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+import org.jeecg.common.desensitization.annotation.SensitiveField;
+
+@Data
+@Accessors(chain = true)
+@EqualsAndHashCode(callSuper = false)
+public class CustomerLoginUser {
+    
+    /**
+     * 客户用户ID
+     */
+    @SensitiveField
+    private String id;
+    
+}

+ 37 - 0
jeecg-module-system/jeecg-shop/pom.xml

@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.jeecgframework.boot</groupId>
+        <artifactId>jeecg-module-system</artifactId>
+        <version>3.6.3</version>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>org.jeecg</groupId>
+    <artifactId>jeecg-shop</artifactId>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.jeecgframework.boot</groupId>
+            <artifactId>jeecg-system-local-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.jeecgframework.boot</groupId>
+            <artifactId>jeecg-system-biz</artifactId>
+            <version>3.6.3</version>
+        </dependency>
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>dysmsapi20170525</artifactId>
+            <version>3.0.0</version>
+        </dependency>
+    </dependencies>
+</project>

+ 37 - 0
jeecg-module-system/jeecg-shop/src/main/java/org/jeecg/modules/shop/captcha/config/EmailConfig.java

@@ -0,0 +1,37 @@
+package org.jeecg.modules.shop.captcha.config;
+
+import cn.hutool.extra.mail.MailAccount;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class EmailConfig {
+    
+    @Value("${aliyun.email.host}")
+    private String host;
+    @Value("${aliyun.email.port}")
+    private int port;
+    @Value("${aliyun.email.ssl}")
+    private boolean sslEnable;
+    @Value("${aliyun.email.from}")
+    private String from;
+    @Value("${aliyun.email.user}")
+    private String user;
+    @Value("${aliyun.email.token}")
+    private String token;
+    
+    @Bean("partner_MailAccount")
+    public MailAccount getMailAccount() {
+        MailAccount account = new MailAccount();
+        account.setHost(host);
+        account.setPort(port);
+        account.setAuth(true);
+        account.setFrom(from);
+        account.setUser(user);
+        account.setPass(token);
+        account.setSslEnable(sslEnable);
+        return account;
+    }
+    
+}

+ 27 - 0
jeecg-module-system/jeecg-shop/src/main/java/org/jeecg/modules/shop/captcha/config/SmsClientConfig.java

@@ -0,0 +1,27 @@
+package org.jeecg.modules.shop.captcha.config;
+
+import com.aliyun.dysmsapi20170525.Client;
+import com.aliyun.teaopenapi.models.Config;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class SmsClientConfig {
+
+    @Value("${aliyun.sms.ak}")
+    private String accessKey;
+
+    @Value("${aliyun.sms.sk}")
+    private String secretKey;
+    
+    @Bean
+    public Client getSmsClient() throws Exception {
+        Config config = new Config()
+                .setAccessKeyId(accessKey)
+                .setAccessKeySecret(secretKey)
+                .setEndpoint("dysmsapi.aliyuncs.com");
+        return new Client(config);
+    }
+    
+}

+ 48 - 0
jeecg-module-system/jeecg-shop/src/main/java/org/jeecg/modules/shop/captcha/controller/CaptchaController.java

@@ -0,0 +1,48 @@
+package org.jeecg.modules.shop.captcha.controller;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.jeecg.common.api.vo.Result;
+import org.jeecg.config.shiro.IgnoreAuth;
+import org.jeecg.modules.shop.captcha.entity.SendSmsCaptchaDTO;
+import org.jeecg.modules.shop.captcha.entity.VerifySmsCaptchaDTO;
+import org.jeecg.modules.shop.captcha.service.ICaptchaService;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.validation.Valid;
+import java.util.HashMap;
+import java.util.Map;
+
+@Slf4j
+@Api(tags = "验证码服务")
+@RestController
+@RequestMapping("/captcha")
+@RequiredArgsConstructor
+public class CaptchaController {
+    
+    private final ICaptchaService service;
+    
+    @IgnoreAuth
+    @PostMapping("/sms/send")
+    @ApiOperation(value = "发送短信验证码", notes = "发送短信验证码")
+    public Result<?> sendSmsCaptcha(@RequestBody @Valid SendSmsCaptchaDTO dto) {
+        service.sendSmsCaptcha(dto);
+        return Result.OK();
+    }
+
+    @IgnoreAuth
+    @PostMapping("/sms/verify")
+    @ApiOperation(value = "验证短信验证码(", notes = "验证短信验证码,验证成功后验证码立即失效")
+    public Result<Map<String, String>> verifySmsCaptcha(@RequestBody @Valid VerifySmsCaptchaDTO dto) {
+        String key = service.verifySmsCaptcha(dto);
+        Map<String, String> map = new HashMap<>();
+        map.put("token", key);
+        return Result.OK(map);
+    }
+    
+}

+ 28 - 0
jeecg-module-system/jeecg-shop/src/main/java/org/jeecg/modules/shop/captcha/entity/SendSmsCaptchaDTO.java

@@ -0,0 +1,28 @@
+package org.jeecg.modules.shop.captcha.entity;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Pattern;
+
+@Data
+@Accessors(chain = true)
+@ApiModel("发送短信/邮件验证码")
+public class SendSmsCaptchaDTO {
+    
+    @ApiModelProperty(value = "邮箱", required = true)
+    @NotBlank(message = "邮箱不能为空")
+    @Pattern(regexp = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
+            message = "请输入正确的手机号或邮箱")
+    private String account;
+    
+    @ApiModelProperty(value = "验证码类型,供应商用户[登录(partner_login)]", required = true)
+    @NotBlank(message = "验证码类型不能为空")
+    private String type;
+
+    private String name;
+
+}

+ 30 - 0
jeecg-module-system/jeecg-shop/src/main/java/org/jeecg/modules/shop/captcha/entity/VerifySmsCaptchaDTO.java

@@ -0,0 +1,30 @@
+package org.jeecg.modules.shop.captcha.entity;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Pattern;
+
+@Data
+@Accessors(chain = true)
+@ApiModel("校验短信验证码")
+public class VerifySmsCaptchaDTO {
+
+    @ApiModelProperty(value = "手机号", required = true)
+    @NotBlank(message = "手机号不能为空")
+    @Pattern(regexp = "^1[3-9]\\d{9}$|^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
+            message = "请输入正确的手机号或邮箱")
+    private String account;
+
+    @ApiModelProperty(value = "验证码类型", required = true)
+    @NotBlank(message = "验证码类型不能为空")
+    private String type;
+
+    @ApiModelProperty(value = "验证码", required = true)
+    @NotBlank(message = "验证码不能为空")
+    private String captcha;
+    
+}

+ 57 - 0
jeecg-module-system/jeecg-shop/src/main/java/org/jeecg/modules/shop/captcha/enums/CaptchaKeyEnum.java

@@ -0,0 +1,57 @@
+package org.jeecg.modules.shop.captcha.enums;
+
+import cn.hutool.crypto.SecureUtil;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@Getter
+@AllArgsConstructor
+public enum CaptchaKeyEnum {
+    
+    // KT 是 Key Template 的缩写
+    /**
+     * 短信验证码 Key 模板
+     */
+    KT_SMS_CAPTCHA("captcha:sms:%s:%s"),
+
+    /**
+     * 邮件验证码 Key 模板
+     */
+    KT_EMAIL_CAPTCHA("captcha:email:%s:%s"),
+
+    /**
+     * 验证码访问次数 Key 模板
+     */
+    KT_CAPTCHA_ACCESS_COUNT("captcha:ac:%s"),
+    /**
+     * 验证码Token Key 模板
+     */
+    KT_CAPTCHA_TOKEN("captcha:token:%s"),
+    /**
+     * 验证码发送锁 Key 模板
+     */
+    KT_CAPTCHA_SEND_LOCK("captcha:lock:%s");
+    
+    private final String value;
+    
+    public static String generateSmsCaptchaKey(String type, String phoneNumber) {
+        return String.format(KT_SMS_CAPTCHA.value, type, SecureUtil.md5(phoneNumber));
+    }
+
+    public static String generateEmailCaptchaKey(String type, String email) {
+        return String.format(KT_EMAIL_CAPTCHA.value, type, SecureUtil.md5(email));
+    }
+
+    public static String generateCaptchaAccessCountKey(String captchaKey) {
+        return String.format(KT_CAPTCHA_ACCESS_COUNT.value, captchaKey);
+    }
+
+    public static String generateCaptchaTokenKey(String captchaKey) {
+        return String.format(KT_CAPTCHA_TOKEN.value, captchaKey);
+    }
+
+    public static String generateCaptchaSendLockKey(String receiver) {
+        return String.format(KT_CAPTCHA_SEND_LOCK.value, SecureUtil.md5(receiver));
+    }
+    
+}

+ 28 - 0
jeecg-module-system/jeecg-shop/src/main/java/org/jeecg/modules/shop/captcha/enums/CaptchaTypeEnum.java

@@ -0,0 +1,28 @@
+package org.jeecg.modules.shop.captcha.enums;
+
+import lombok.Getter;
+
+@Getter
+public enum CaptchaTypeEnum {
+
+    /**
+     * 合作伙伴登录
+     */
+    SUPPLIER_LOGIN("partner_login");
+
+
+    private final String type;
+    
+    CaptchaTypeEnum(String type) {
+        this.type = type;
+    }
+    
+    public static boolean isValidType(String type) {
+        for (CaptchaTypeEnum e : values()) {
+            if (e.getType().equals(type)) {
+                return true;
+            }
+        }
+        return false;
+    }
+}

+ 21 - 0
jeecg-module-system/jeecg-shop/src/main/java/org/jeecg/modules/shop/captcha/service/ICaptchaService.java

@@ -0,0 +1,21 @@
+package org.jeecg.modules.shop.captcha.service;
+
+
+import org.jeecg.modules.shop.captcha.entity.SendSmsCaptchaDTO;
+import org.jeecg.modules.shop.captcha.entity.VerifySmsCaptchaDTO;
+
+public interface ICaptchaService {
+    
+    void sendSmsCaptcha(SendSmsCaptchaDTO dto);
+
+    void sendNotifiedCaptcha(SendSmsCaptchaDTO dto);
+    
+    String verifySmsCaptcha(VerifySmsCaptchaDTO dto);
+
+    void verifyToken(String captchaKey, String token);
+    
+    String getCaptcha(String captchaKey, int maxAccessCount);
+    
+    void clearCaptcha(String captchaKey);
+    
+}

+ 154 - 0
jeecg-module-system/jeecg-shop/src/main/java/org/jeecg/modules/shop/captcha/service/impl/CaptchaServiceImpl.java

@@ -0,0 +1,154 @@
+package org.jeecg.modules.shop.captcha.service.impl;
+
+import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.IdUtil;
+import cn.hutool.core.util.StrUtil;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.jeecg.modules.shop.captcha.entity.SendSmsCaptchaDTO;
+import org.jeecg.modules.shop.captcha.entity.VerifySmsCaptchaDTO;
+import org.jeecg.modules.shop.captcha.enums.CaptchaKeyEnum;
+import org.jeecg.modules.shop.captcha.enums.CaptchaTypeEnum;
+import org.jeecg.modules.shop.captcha.service.ICaptchaService;
+import org.jeecg.modules.shop.captcha.util.EmailUtil;
+import org.jeecg.modules.shop.captcha.util.SmsUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.script.DefaultRedisScript;
+import org.springframework.stereotype.Service;
+
+import java.util.Arrays;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.TimeUnit;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class CaptchaServiceImpl implements ICaptchaService {
+    
+
+    private final RedisTemplate<String, String> redisTemplate;
+    
+    private final SmsUtil smsUtil;
+
+    private final EmailUtil emailUtil;
+
+
+    
+    @Override
+    public void sendSmsCaptcha(SendSmsCaptchaDTO dto) {
+        // ************************************************
+        // * 流控和限额都在阿里云那边配置了,这边不再重复校验
+        // ************************************************
+        Assert.isTrue(CaptchaTypeEnum.isValidType(dto.getType()), "不是有效的验证码类型");
+        String captcha = generateCaptcha();
+//        if (StrUtil.equalsAny(dto.getType(), CaptchaTypeEnum.SUPPLIER_LOGIN.getType(), CaptchaTypeEnum.SUPPLIER_FORGOT_PASSWORD.getType())) {
+//            Assert.isTrue(customerService.count(new LambdaQueryWrapper<S003Customer>().eq(S003Customer::getEmail, dto.getAccount()))>0, "邮箱未注册");
+//        } else if (StrUtil.equals(dto.getType(), CaptchaTypeEnum.SUPPLIER_REGISTER.getType())) {
+//            Assert.isFalse(customerService.count(new LambdaQueryWrapper<S003Customer>().eq(S003Customer::getEmail, dto.getAccount()))<=1, "邮箱已注册");
+//        }
+        Assert.isTrue(tryLock(dto.getAccount()), "流控");
+        emailUtil.sendRegisterEmail(dto.getAccount(),captcha);
+        String cacheKey = CaptchaKeyEnum.generateEmailCaptchaKey(dto.getType(), dto.getAccount());
+        redisTemplate.opsForValue().set(cacheKey, captcha, 10, TimeUnit.MINUTES);
+    }
+
+    @Override
+    public void sendNotifiedCaptcha(SendSmsCaptchaDTO dto) {
+        Assert.isTrue(CaptchaTypeEnum.isValidType(dto.getType()), "不是有效的验证码类型");
+        //分为手机号和邮箱的情况
+        if (StrUtil.containsAny(dto.getAccount(), "@")) {
+            Assert.isTrue(tryLock(dto.getAccount()), "流控");
+            emailUtil.sendNotifiedEmail(dto.getAccount(), dto.getName());
+        }else {
+            try {
+                Assert.isTrue(tryLock(dto.getAccount()), "流控");
+                smsUtil.sendSmsNotified(dto.getAccount(), dto.getName());
+            } catch (Exception e) {
+                String errMsg = "验证码发送失败";
+                if (StrUtil.contains(e.getMessage(), "流控")) {
+                    errMsg += ",触发流控限制";
+                }
+                throw new RuntimeException(errMsg);
+            }
+        }
+    }
+
+    @Override
+    public String verifySmsCaptcha(VerifySmsCaptchaDTO dto) {
+        Assert.isTrue(CaptchaTypeEnum.isValidType(dto.getType()), "参数错误");
+        int maxAccessCount = 5;
+        String captchaKey = "";
+        String captcha = "";
+        //分手机号和邮件两张情况
+        if (StrUtil.containsAny(dto.getAccount(), "@")){
+            captchaKey = CaptchaKeyEnum.generateEmailCaptchaKey(dto.getType(), dto.getAccount());
+            captcha = getCaptcha(captchaKey, maxAccessCount);
+        }else{
+            captchaKey = CaptchaKeyEnum.generateSmsCaptchaKey(dto.getType(), dto.getAccount());
+            captcha = getCaptcha(captchaKey, maxAccessCount);
+        }
+        Assert.notBlank(captcha, "验证码错误");
+        Assert.isTrue(StrUtil.equals(dto.getCaptcha(), captcha), "验证码错误");
+        String token = IdUtil.simpleUUID();
+        String tokenKey = CaptchaKeyEnum.generateCaptchaTokenKey(captchaKey);
+        redisTemplate.opsForValue().set(tokenKey, token, 10, TimeUnit.MINUTES);
+        clearCaptcha(captchaKey);
+        return token;
+    }
+
+    @Override
+    public void verifyToken(String captchaKey, String token) {
+        Assert.isTrue(StrUtil.isAllNotEmpty(captchaKey, token), "参数错误");
+        String tokenKey = CaptchaKeyEnum.generateCaptchaTokenKey(captchaKey);
+        String _token = redisTemplate.opsForValue().get(tokenKey);
+        Assert.isTrue(StrUtil.equals(_token, token), "token无效");
+        redisTemplate.delete(tokenKey);
+    }
+
+    @Override
+    public String getCaptcha(String captchaKey, int maxAccessCount) {
+        Assert.notEmpty(captchaKey, "captchaKey 不能为空");
+        Assert.isTrue(maxAccessCount > 0, "maxAccessCount 必须大于 0");
+        String script = "local captchaKey = KEYS[1]\n" +
+                "local countKey = KEYS[2]\n" +
+                "local captcha = redis.call('GET', captchaKey)\n" +
+                "if not captcha then\n" +
+                "    return nil\n" +
+                "end\n" +
+                "local currentCount = tonumber(redis.call('GET', countKey))\n" +
+                "if not currentCount or currentCount < tonumber(ARGV[1]) then\n" +
+                "    redis.call('INCR', countKey)\n" +
+                "    local captchaKeyTTL = redis.call('TTL', captchaKey)\n" +
+                "    if captchaKeyTTL < 0 then\n" +
+                "        captchaKeyTTL = 600\n" +
+                "    end\n" +
+                "    redis.call('EXPIRE', countKey, captchaKeyTTL)\n" +
+                "    return captcha\n" +
+                "else\n" +
+                "    redis.call('DEL', captchaKey)\n" +
+                "    redis.call('DEL', countKey)\n" +
+                "    return nil\n" +
+                "end";
+        String countKey = CaptchaKeyEnum.generateCaptchaAccessCountKey(captchaKey);
+        DefaultRedisScript<String> redisScript = new DefaultRedisScript<>(script, String.class);
+        return redisTemplate.execute(redisScript, Arrays.asList(captchaKey, countKey), String.valueOf(maxAccessCount));
+    }
+
+    @Override
+    public void clearCaptcha(String captchaKey) {
+        String countKey = CaptchaKeyEnum.generateCaptchaAccessCountKey(captchaKey);
+        redisTemplate.delete(Arrays.asList(captchaKey, countKey));
+    }
+
+    private String generateCaptcha() {
+        return String.valueOf(ThreadLocalRandom.current().nextInt(100000, 999999));
+    }
+    
+    private boolean tryLock(String receiver) {
+        Boolean isLock = redisTemplate.opsForValue().setIfAbsent(CaptchaKeyEnum.generateCaptchaSendLockKey(receiver), "1", 1, TimeUnit.MINUTES);
+        return Boolean.TRUE.equals(isLock);
+    }
+    
+}

+ 142 - 0
jeecg-module-system/jeecg-shop/src/main/java/org/jeecg/modules/shop/captcha/util/EmailUtil.java

@@ -0,0 +1,142 @@
+package org.jeecg.modules.shop.captcha.util;
+
+import cn.hutool.core.lang.Assert;
+import cn.hutool.crypto.SecureUtil;
+import cn.hutool.extra.mail.MailAccount;
+import cn.hutool.extra.mail.MailUtil;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.script.DefaultRedisScript;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class EmailUtil {
+
+    @Value("${aliyun.email.limit.single-of-hour}")
+    private int singleEmailLimitOfHour;
+    @Value("${aliyun.email.limit.single-of-day}")
+    private int singleEmailLimitOfDay;
+    @Value("${aliyun.email.limit.total-of-hour}")
+    private int totalEmailLimitOfHour;
+    @Value("${aliyun.email.limit.total-of-day}")
+    private int totalEmailLimitOfDay;
+    
+    @Resource(name = "partner_MailAccount")
+    private MailAccount account;
+    
+    private final RedisTemplate<String, String> redisTemplate;
+
+    private static final String TYPE_LOGIN = "登录";
+    private static final String TITLE_LOGIN = "Your EJET Spark Code Is %s";
+    private static final String CT_LOGIN = "Welcome! Enter this code within the next 10 minutes to log in:\n" +
+            "%s" +"<br/><br/>"+
+            "You're receiving this email because you are creating or logging in to your EJET Spark account. " +
+            "This email isnot a marketing or promotional email. That is why this email does not contain an unsubscribe link." +
+            " lf youhave any problems logging in, please contact us via spark@ejet.com for help.";
+
+
+
+    private static final String TYPE_NOTIFIED = "合作伙伴审核通知";
+    private static final String TITLE_NOTIFIED = "EJET 供应商协同平台";
+    private static final String CT_NOTIFIED = "您提交的s%供应商信息已通过,请及时登录平台进行产品/品牌录入 https://supplier.ejetgroup.com/login";
+
+
+
+    public void sendRegisterEmail(String email,String captcha) {
+        sendEmail(email, String.format(TITLE_LOGIN,captcha), String.format(CT_LOGIN, captcha), TYPE_LOGIN);
+    }
+    
+
+
+    public void sendNotifiedEmail(String email, String supplierName) {
+        sendEmail(email, TITLE_NOTIFIED, String.format(CT_NOTIFIED, supplierName), TYPE_NOTIFIED);
+    }
+
+
+    
+    private void sendEmail(String email, String title, String content, String type) {
+        Assert.isFalse(isTriggerSendFrequencyLimit(email), "触发发送频率限制,请稍后重试");
+        try {
+            log.info("发送{}邮件,邮箱={}", type, email);
+            MailUtil.send(account, email, title, content, true);
+            onEmailSendSuccess(email);
+            log.info("邮件发送成功");
+        } catch (Exception e) {
+            log.error("邮件发送失败,邮箱={},错误信息={}", email, e.getMessage());
+        }
+    }
+
+    private boolean isTriggerSendFrequencyLimit(String email) {
+        Boolean isLock = redisTemplate.opsForValue().setIfAbsent(generateSendFrequencyLimitKey(email, TimeUnit.MINUTES), "1", 1, TimeUnit.MINUTES);
+        if (!Boolean.TRUE.equals(isLock)) {
+            return true;
+        }
+        List<String> values = redisTemplate.opsForValue().multiGet(Arrays.asList(
+                generateSendFrequencyLimitKey(email, TimeUnit.HOURS),
+                generateSendFrequencyLimitKey(email, TimeUnit.DAYS),
+                generateSendFrequencyLimitKey(null, TimeUnit.HOURS),
+                generateSendFrequencyLimitKey(null, TimeUnit.DAYS)));
+        if (values == null) {
+            log.error("redis访问异常,values == null");
+        }
+        int hourCount = values.get(0) == null ? 0 : Integer.parseInt(values.get(0));
+        int dayCount = values.get(1) == null ? 0 : Integer.parseInt(values.get(1));
+        int totalHourCount = values.get(2) == null ? 0 : Integer.parseInt(values.get(2));
+        int totalDayCount = values.get(3) == null ? 0 : Integer.parseInt(values.get(3));
+        if (hourCount >= singleEmailLimitOfHour || dayCount >= singleEmailLimitOfDay) {
+            return true;
+        }
+        if (totalEmailLimitOfHour > 0 && totalHourCount >= totalEmailLimitOfHour) {
+            log.error("邮件全局发送量触发小时级限额,当前限额值={}", totalEmailLimitOfHour);
+            return true;
+        }
+        if (totalEmailLimitOfDay > 0 && totalDayCount >= totalEmailLimitOfDay) {
+            log.error("邮件全局发送量触发天级限额,当前限额值={}", totalEmailLimitOfDay);
+            return true;
+        }
+        return false;
+    }
+
+    private void onEmailSendSuccess(String email) {
+        String script = "redis.call('INCR', KEYS[1])\nredis.call('EXPIRE', KEYS[1], 3600)\n" +
+                "redis.call('INCR', KEYS[2])\nredis.call('EXPIRE', KEYS[2], 86400)\n" +
+                "redis.call('INCR', KEYS[3])\nredis.call('EXPIRE', KEYS[3], 3600)\n" +
+                "redis.call('INCR', KEYS[4])\nredis.call('EXPIRE', KEYS[4], 86400)";
+        DefaultRedisScript<String> redisScript = new DefaultRedisScript<>(script, String.class);
+        redisTemplate.execute(redisScript, Arrays.asList(
+                generateSendFrequencyLimitKey(email, TimeUnit.HOURS),
+                generateSendFrequencyLimitKey(email, TimeUnit.DAYS),
+                generateSendFrequencyLimitKey(null, TimeUnit.HOURS),
+                generateSendFrequencyLimitKey(null, TimeUnit.DAYS)));
+    }
+
+    private String generateSendFrequencyLimitKey(String email, TimeUnit timeUnit) {
+        String timeUnitStr;
+        switch (timeUnit) {
+            case MINUTES:
+                timeUnitStr = "F";
+                break;
+            case HOURS:
+                timeUnitStr = String.valueOf(LocalDateTime.now().getHour());
+                break;
+            case DAYS:
+                timeUnitStr = String.valueOf(LocalDate.now().getDayOfMonth());
+                break;
+            default:
+                throw new IllegalArgumentException();
+        }
+        return String.format("email:limit:%s:%s", timeUnitStr, email != null ? SecureUtil.md5(email) : "total");
+    }
+    
+}

+ 56 - 0
jeecg-module-system/jeecg-shop/src/main/java/org/jeecg/modules/shop/captcha/util/SmsUtil.java

@@ -0,0 +1,56 @@
+package org.jeecg.modules.shop.captcha.util;
+
+import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.PhoneUtil;
+import cn.hutool.core.util.StrUtil;
+import com.aliyun.dysmsapi20170525.Client;
+import com.aliyun.dysmsapi20170525.models.SendSmsRequest;
+import com.aliyun.dysmsapi20170525.models.SendSmsResponse;
+import com.aliyun.teautil.models.RuntimeOptions;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class SmsUtil {
+
+    private final Client smsClient;
+    
+    public void sendSmsCaptcha(String phoneNumber, String captcha) {
+        Assert.isTrue(StrUtil.isAllNotEmpty(phoneNumber, captcha) && PhoneUtil.isMobile(phoneNumber) && StrUtil.isNumeric(captcha), "参数错误");
+        SendSmsRequest request = new SendSmsRequest()
+                .setPhoneNumbers(phoneNumber)
+                .setSignName("杭州柏杰科技")
+                .setTemplateCode("SMS_468890012")
+                .setTemplateParam(String.format("{\"code\":\"%s\"}", captcha));
+        try {
+            log.info("发送短信验证码,手机号={}", phoneNumber);
+            SendSmsResponse res = smsClient.sendSmsWithOptions(request, new RuntimeOptions());
+            Assert.isTrue("OK".equals(res.body.code), res.body.message);
+            log.info("短信验证码发送成功");
+        } catch (Exception e) {
+            log.error("短信验证码发送失败,手机号={},验证码={},错误信息={}", phoneNumber, captcha, e.getMessage());
+            throw new RuntimeException(e.getMessage());
+        }
+    }
+
+    public void sendSmsNotified(String phoneNumber, String name) {
+        Assert.isTrue(StrUtil.isAllNotEmpty(phoneNumber, name) && PhoneUtil.isMobile(phoneNumber), "参数错误");
+        SendSmsRequest request = new SendSmsRequest()
+                .setPhoneNumbers(phoneNumber)
+                .setSignName("杭州柏杰科技")
+                .setTemplateCode("SMS_483425393")
+                .setTemplateParam(String.format("{\"name\":\"%s\"}", name));
+        try {
+            log.info("发送短信验证码,手机号={}", phoneNumber);
+            SendSmsResponse res = smsClient.sendSmsWithOptions(request, new RuntimeOptions());
+            Assert.isTrue("OK".equals(res.body.code), res.body.message);
+            log.info("短信验证码发送成功");
+        } catch (Exception e) {
+            log.error("短信验证码发送失败,手机号={},供应商名称={},错误信息={}", phoneNumber, name, e.getMessage());
+            throw new RuntimeException(e.getMessage());
+        }
+    }
+}

+ 91 - 0
jeecg-module-system/jeecg-shop/src/main/java/org/jeecg/modules/shop/user/controller/PartnerUserController.java

@@ -0,0 +1,91 @@
+package org.jeecg.modules.shop.user.controller;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.Valid;
+
+import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import org.jeecg.common.api.vo.Result;
+import org.jeecg.common.constant.CommonConstant;
+import org.jeecg.common.system.query.QueryGenerator;
+import org.jeecg.common.util.oConvertUtils;
+
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.extern.slf4j.Slf4j;
+
+import org.jeecg.config.shiro.IgnoreAuth;
+import org.jeecg.modules.shop.user.entity.PartnerUser;
+import org.jeecg.modules.shop.user.entity.dto.UserInfoDTO;
+import org.jeecg.modules.shop.user.entity.dto.UserLoginDTO;
+import org.jeecg.modules.shop.user.entity.vo.UserInfoVO;
+import org.jeecg.modules.shop.user.entity.vo.UserLoginVO;
+import org.jeecg.modules.shop.user.service.IPartnerUserService;
+
+import org.jeecg.common.system.base.controller.JeecgController;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+import org.springframework.web.multipart.MultipartHttpServletRequest;
+import org.springframework.web.servlet.ModelAndView;
+import com.alibaba.fastjson.JSON;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.jeecg.common.aspect.annotation.AutoLog;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+
+ /**
+ * @Description: partner_user
+ * @Author: jeecg-boot
+ * @Date:   2025-07-09
+ * @Version: V1.0
+ */
+@Api(tags="[合作伙伴前台]-合作伙伴前台用户接口")
+@RestController
+@RequestMapping("/client/partnerUser")
+@Slf4j
+public class PartnerUserController extends JeecgController<PartnerUser, IPartnerUserService> {
+	@Autowired
+	private IPartnerUserService partnerUserService;
+
+	 @ApiOperation(value = "登录", notes = "登录。<span style='color:red'>注:仅当用户状态为正常时,才会签发token,请注意判断。</span>")
+	 @PostMapping(value = "/login")
+	 @IgnoreAuth
+	 public Result<UserLoginVO> login(@RequestBody @Valid UserLoginDTO loginDTO) {
+		 UserLoginVO loginVO = partnerUserService.login(loginDTO);
+		 return Result.OK(loginVO);
+	 }
+
+	 @ApiOperation(value = "退出登录", notes = "退出登录")
+	 @GetMapping(value = "/logout")
+	 public Result<?> logout(HttpServletRequest request) {
+		 String token = request.getHeader(CommonConstant.X_ACCESS_TOKEN);
+		 partnerUserService.logout(token);
+		 return Result.OK();
+	 }
+
+	 @ApiOperation(value = "查询个人信息", notes = "查询个人信息")
+	 @GetMapping(value = "/userinfo")
+	 public Result<UserInfoVO> getUserInfo() {
+		 return Result.OK(partnerUserService.getUserInfo());
+	 }
+
+
+	 @ApiOperation(value = "更新个人信息", notes = "更新个人信息")
+	 @PostMapping(value = "/userinfo")
+	 public Result<?> updateUserinfo(@RequestBody @Valid UserInfoDTO dto) {
+		 partnerUserService.updateUserInfo(dto);
+		 return Result.OK();
+	 }
+}

+ 170 - 0
jeecg-module-system/jeecg-shop/src/main/java/org/jeecg/modules/shop/user/entity/PartnerUser.java

@@ -0,0 +1,170 @@
+package org.jeecg.modules.shop.user.entity;
+
+import java.io.Serializable;
+import java.io.UnsupportedEncodingException;
+import java.util.Date;
+import java.math.BigDecimal;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import lombok.Data;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import org.springframework.format.annotation.DateTimeFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+/**
+ * @Description: partner_user
+ * @Author: jeecg-boot
+ * @Date:   2025-07-09
+ * @Version: V1.0
+ */
+@Data
+@TableName("partner_user")
+@Accessors(chain = true)
+@EqualsAndHashCode(callSuper = false)
+@ApiModel(value="partner_user对象", description="partner_user")
+public class PartnerUser implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+	/**创建人*/
+    @ApiModelProperty(value = "创建人")
+    private String createBy;
+	/**创建时间*/
+	@JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
+    @ApiModelProperty(value = "创建时间")
+    private Date createTime;
+	/**更新人*/
+    @ApiModelProperty(value = "更新人")
+    private String updateBy;
+	/**更新时间*/
+	@JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
+    @ApiModelProperty(value = "更新时间")
+    private Date updateTime;
+	/**id*/
+	@TableId(type = IdType.ASSIGN_ID)
+    @ApiModelProperty(value = "id")
+    private String id;
+	/**编号*/
+    @ApiModelProperty(value = "编号")
+    private String billNo;
+	/**代理角色code*/
+    @ApiModelProperty(value = "代理角色code")
+    private String ruleCode;
+	/**类型 0个人 1公司*/
+    @ApiModelProperty(value = "类型 0个人 1公司")
+    private Integer type;
+	/**所属部门*/
+    @ApiModelProperty(value = "所属部门")
+    private String sysOrgCode;
+	/**邮箱*/
+    @ApiModelProperty(value = "邮箱")
+    private String email;
+	/**手机区号*/
+    @ApiModelProperty(value = "手机区号")
+    private String areaCode;
+	/**手机号*/
+    @ApiModelProperty(value = "手机号")
+    private String phone;
+	/**全名*/
+    @ApiModelProperty(value = "全名")
+    private String fullName;
+	/**firstName*/
+    @ApiModelProperty(value = "firstName")
+    private String firstName;
+	/**lastName*/
+    @ApiModelProperty(value = "lastName")
+    private String lastName;
+	/**头像*/
+    @ApiModelProperty(value = "头像")
+    private String photo;
+	/**密码*/
+    @ApiModelProperty(value = "密码")
+    private String password;
+	/**国家*/
+    @ApiModelProperty(value = "国家")
+    private String country;
+	/**申请理由*/
+    @ApiModelProperty(value = "申请理由")
+    private String applicationReason;
+	/**促进易杰发展*/
+    @ApiModelProperty(value = "促进易杰发展")
+    private String promoteBusiness;
+	/**0paypal 1bank*/
+    @ApiModelProperty(value = "0paypal 1bank")
+    private Integer payMethod;
+	/**paypal email*/
+    @ApiModelProperty(value = "paypal email")
+    private String paypalEmail;
+	/**paypal_beneficiarys_name*/
+    @ApiModelProperty(value = "paypal_beneficiarys_name")
+    private String paypalBeneficiarysName;
+	/**bank_account_name*/
+    @ApiModelProperty(value = "bank_account_name")
+    private String bankAccountName;
+	/**bank_account_number*/
+    @ApiModelProperty(value = "bank_account_number")
+    private String bankAccountNumber;
+	/**back card-SWIFT/BIC Code*/
+    @ApiModelProperty(value = "back card-SWIFT/BIC Code")
+    private String bankCardCode;
+	/**back card-BANK NAME*/
+    @ApiModelProperty(value = "back card-BANK NAME")
+    private String bankName;
+	/**bank_address*/
+    @ApiModelProperty(value = "bank_address")
+    private String bankAddress;
+	/**Corporate Name 类型为公司时填写*/
+    @ApiModelProperty(value = "Corporate Name 类型为公司时填写")
+    private String companyName;
+	/**Position类型为公司时填写*/
+    @ApiModelProperty(value = "Position类型为公司时填写")
+    private String position;
+	/**Date of Incorporation类型为公司时填写*/
+	@JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
+    @ApiModelProperty(value = "Date of Incorporation类型为公司时填写")
+    private Date dateOfIncorporation;
+	/**Company Size类型为公司时填写*/
+    @ApiModelProperty(value = "Company Size类型为公司时填写")
+    private String ompanySize;
+	/**Business type类型为公司时填写*/
+    @ApiModelProperty(value = "Business type类型为公司时填写")
+    private String businessType;
+	/**状态0禁用1启用*/
+    @ApiModelProperty(value = "状态0禁用1启用")
+    private Integer state;
+	/**登录次数*/
+    @ApiModelProperty(value = "登录次数")
+    private Integer loginCount;
+	/**最近登录时间*/
+    @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
+    @ApiModelProperty(value = "最近登录时间")
+    private Date loginTime;
+	/**审核时间*/
+	@JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
+    @ApiModelProperty(value = "审核时间")
+    private Date auditTime;
+	/**审核人*/
+    @ApiModelProperty(value = "审核人")
+    private String auditPerson;
+
+    @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
+    @ApiModelProperty(value = "注册时间")
+    private Date registerTime;
+
+    @ApiModelProperty(value = "信息是否完善0未完善 1已完善")
+    private Integer infoState;
+
+    @ApiModelProperty(value = "审核状态0审核中 1拒绝 2已通过")
+    private Integer auditState;
+
+}

+ 82 - 0
jeecg-module-system/jeecg-shop/src/main/java/org/jeecg/modules/shop/user/entity/dto/UserInfoDTO.java

@@ -0,0 +1,82 @@
+package org.jeecg.modules.shop.user.entity.dto;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import javax.validation.constraints.NotBlank;
+import java.util.Date;
+
+@Data
+public class UserInfoDTO {
+
+    /**手机区号*/
+    @ApiModelProperty(value = "手机区号")
+    private String areaCode;
+    /**手机号*/
+    @ApiModelProperty(value = "手机号")
+    private String phone;
+    /**全名*/
+    @ApiModelProperty(value = "全名")
+    private String fullName;
+    /**firstName*/
+    @ApiModelProperty(value = "firstName")
+    private String firstName;
+    /**lastName*/
+    @ApiModelProperty(value = "lastName")
+    private String lastName;
+    /**头像*/
+    @ApiModelProperty(value = "头像")
+    private String photo;
+    /**密码*/
+    @ApiModelProperty(value = "密码")
+    private String password;
+    /**国家*/
+    @ApiModelProperty(value = "国家")
+    private String country;
+
+    /**0paypal 1bank*/
+    @ApiModelProperty(value = "0paypal 1bank")
+    private Integer payMethod;
+    /**paypal email*/
+    @ApiModelProperty(value = "paypal email")
+    private String paypalEmail;
+    /**paypal_beneficiarys_name*/
+    @ApiModelProperty(value = "paypal_beneficiarys_name")
+    private String paypalBeneficiarysName;
+    /**bank_account_name*/
+    @ApiModelProperty(value = "bank_account_name")
+    private String bankAccountName;
+    /**bank_account_number*/
+    @ApiModelProperty(value = "bank_account_number")
+    private String bankAccountNumber;
+    /**back card-SWIFT/BIC Code*/
+    @ApiModelProperty(value = "back card-SWIFT/BIC Code")
+    private String bankCardCode;
+    /**back card-BANK NAME*/
+    @ApiModelProperty(value = "back card-BANK NAME")
+    private String bankName;
+    /**bank_address*/
+    @ApiModelProperty(value = "bank_address")
+    private String bankAddress;
+    /**Corporate Name 类型为公司时填写*/
+    @ApiModelProperty(value = "Corporate Name 类型为公司时填写")
+    private String companyName;
+    /**Position类型为公司时填写*/
+    @ApiModelProperty(value = "Position类型为公司时填写")
+    private String position;
+    /**Date of Incorporation类型为公司时填写*/
+    @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
+    @ApiModelProperty(value = "Date of Incorporation类型为公司时填写")
+    private Date dateOfIncorporation;
+    /**Company Size类型为公司时填写*/
+    @ApiModelProperty(value = "Company Size类型为公司时填写")
+    private String ompanySize;
+    /**Business type类型为公司时填写*/
+    @ApiModelProperty(value = "Business type类型为公司时填写")
+    private String businessType;
+}

+ 26 - 0
jeecg-module-system/jeecg-shop/src/main/java/org/jeecg/modules/shop/user/entity/dto/UserLoginDTO.java

@@ -0,0 +1,26 @@
+package org.jeecg.modules.shop.user.entity.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.Email;
+import javax.validation.constraints.NotBlank;
+
+@Data
+public class UserLoginDTO {
+
+    /**
+     * 邮箱
+     */
+    @ApiModelProperty(value = "邮箱")
+    @NotBlank(message = "Invalid email")
+    @Email(message = "Invalid email")
+    private String email;
+
+    /**
+     * 验证码
+     */
+    @NotBlank(message = "Invalid captcha")
+    @ApiModelProperty(value = "验证码")
+    private String captcha;
+}

+ 96 - 0
jeecg-module-system/jeecg-shop/src/main/java/org/jeecg/modules/shop/user/entity/vo/UserInfoVO.java

@@ -0,0 +1,96 @@
+package org.jeecg.modules.shop.user.entity.vo;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.jeecg.common.aspect.annotation.Dict;
+import org.jeecg.common.aspect.annotation.EnableDictTranslate;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.util.Date;
+
+@Data
+@EnableDictTranslate
+public class UserInfoVO {
+
+    /**类型 0个人 1公司*/
+    @ApiModelProperty(value = "类型 0个人 1公司")
+    private Integer type;
+    /**邮箱*/
+    @ApiModelProperty(value = "邮箱")
+    private String email;
+    /**手机区号*/
+    @ApiModelProperty(value = "手机区号")
+    private String areaCode;
+    /**手机号*/
+    @ApiModelProperty(value = "手机号")
+    private String phone;
+    /**全名*/
+    @ApiModelProperty(value = "全名")
+    private String fullName;
+    /**firstName*/
+    @ApiModelProperty(value = "firstName")
+    private String firstName;
+    /**lastName*/
+    @ApiModelProperty(value = "lastName")
+    private String lastName;
+    /**头像*/
+    @ApiModelProperty(value = "头像")
+    private String photo;
+    /**密码*/
+    @ApiModelProperty(value = "密码")
+    private String password;
+    /**国家*/
+    @ApiModelProperty(value = "国家")
+    private String country;
+    /**申请理由*/
+    @ApiModelProperty(value = "申请理由")
+    private String applicationReason;
+    /**促进易杰发展*/
+    @ApiModelProperty(value = "促进易杰发展")
+    private String promoteBusiness;
+    /**0paypal 1bank*/
+    @ApiModelProperty(value = "0paypal 1bank")
+    private Integer payMethod;
+    /**paypal email*/
+    @ApiModelProperty(value = "paypal email")
+    private String paypalEmail;
+    /**paypal_beneficiarys_name*/
+    @ApiModelProperty(value = "paypal_beneficiarys_name")
+    private String paypalBeneficiarysName;
+    /**bank_account_name*/
+    @ApiModelProperty(value = "bank_account_name")
+    private String bankAccountName;
+    /**bank_account_number*/
+    @ApiModelProperty(value = "bank_account_number")
+    private String bankAccountNumber;
+    /**back card-SWIFT/BIC Code*/
+    @ApiModelProperty(value = "back card-SWIFT/BIC Code")
+    private String bankCardCode;
+    /**back card-BANK NAME*/
+    @ApiModelProperty(value = "back card-BANK NAME")
+    private String bankName;
+    /**bank_address*/
+    @ApiModelProperty(value = "bank_address")
+    private String bankAddress;
+    /**Corporate Name 类型为公司时填写*/
+    @ApiModelProperty(value = "Corporate Name 类型为公司时填写")
+    private String companyName;
+    /**Position类型为公司时填写*/
+    @ApiModelProperty(value = "Position类型为公司时填写")
+    private String position;
+    /**Date of Incorporation类型为公司时填写*/
+    @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
+    @ApiModelProperty(value = "Date of Incorporation类型为公司时填写")
+    private Date dateOfIncorporation;
+    /**Company Size类型为公司时填写*/
+    @ApiModelProperty(value = "Company Size类型为公司时填写")
+    private String ompanySize;
+    /**Business type类型为公司时填写*/
+    @ApiModelProperty(value = "Business type类型为公司时填写")
+    private String businessType;
+    
+}

+ 21 - 0
jeecg-module-system/jeecg-shop/src/main/java/org/jeecg/modules/shop/user/entity/vo/UserLoginVO.java

@@ -0,0 +1,21 @@
+package org.jeecg.modules.shop.user.entity.vo;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+@Data
+public class UserLoginVO {
+
+    /**
+     * token
+     */
+    @ApiModelProperty(value = "token")
+    private String token;
+
+    /**
+     * 用户信息
+     */
+    @ApiModelProperty(value = "用户信息")
+    private UserInfoVO userInfo;
+    
+}

+ 18 - 0
jeecg-module-system/jeecg-shop/src/main/java/org/jeecg/modules/shop/user/mapper/PartnerUserMapper.java

@@ -0,0 +1,18 @@
+package org.jeecg.modules.shop.user.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.jeecg.modules.shop.user.entity.PartnerUser;
+
+import java.util.List;
+
+
+
+/**
+ * @Description: partner_user
+ * @Author: jeecg-boot
+ * @Date:   2025-07-09
+ * @Version: V1.0
+ */
+public interface PartnerUserMapper extends BaseMapper<PartnerUser> {
+
+}

+ 5 - 0
jeecg-module-system/jeecg-shop/src/main/java/org/jeecg/modules/shop/user/mapper/xml/PartnerUserMapper.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.jeecg.modules.shop.user.mapper.PartnerUserMapper">
+
+</mapper>

+ 25 - 0
jeecg-module-system/jeecg-shop/src/main/java/org/jeecg/modules/shop/user/service/IPartnerUserService.java

@@ -0,0 +1,25 @@
+package org.jeecg.modules.shop.user.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import org.jeecg.modules.shop.user.entity.PartnerUser;
+import org.jeecg.modules.shop.user.entity.dto.UserInfoDTO;
+import org.jeecg.modules.shop.user.entity.dto.UserLoginDTO;
+import org.jeecg.modules.shop.user.entity.vo.UserInfoVO;
+import org.jeecg.modules.shop.user.entity.vo.UserLoginVO;
+
+/**
+ * @Description: partner_user
+ * @Author: jeecg-boot
+ * @Date:   2025-07-09
+ * @Version: V1.0
+ */
+public interface IPartnerUserService extends IService<PartnerUser> {
+
+    UserLoginVO login(UserLoginDTO loginDTO);
+
+    void logout(String token);
+
+    UserInfoVO getUserInfo();
+
+    void updateUserInfo(UserInfoDTO dto);
+}

+ 127 - 0
jeecg-module-system/jeecg-shop/src/main/java/org/jeecg/modules/shop/user/service/impl/PartnerUserServiceImpl.java

@@ -0,0 +1,127 @@
+package org.jeecg.modules.shop.user.service.impl;
+
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.IdWorker;
+import lombok.RequiredArgsConstructor;
+import org.jeecg.modules.sequence.service.IMiddleSequenceService;
+import org.jeecg.config.shiro.util.ShopTokenUtil;
+import org.jeecg.modules.shop.captcha.enums.CaptchaKeyEnum;
+import org.jeecg.modules.shop.captcha.enums.CaptchaTypeEnum;
+import org.jeecg.modules.shop.captcha.service.ICaptchaService;
+import org.jeecg.modules.shop.user.entity.PartnerUser;
+import org.jeecg.modules.shop.user.entity.dto.UserInfoDTO;
+import org.jeecg.modules.shop.user.entity.dto.UserLoginDTO;
+import org.jeecg.modules.shop.user.entity.vo.UserInfoVO;
+import org.jeecg.modules.shop.user.entity.vo.UserLoginVO;
+import org.jeecg.modules.shop.user.mapper.PartnerUserMapper;
+import org.jeecg.modules.shop.user.service.IPartnerUserService;
+import org.jeecg.modules.shop.user.util.CustomerUserHolder;
+import org.springframework.beans.BeanUtils;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Service;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @Description: partner_user
+ * @Author: jeecg-boot
+ * @Date:   2025-07-09
+ * @Version: V1.0
+ */
+@Service
+@RequiredArgsConstructor
+public class PartnerUserServiceImpl extends ServiceImpl<PartnerUserMapper, PartnerUser> implements IPartnerUserService {
+
+    private final ICaptchaService captchaService;
+
+    private final IMiddleSequenceService middleSequenceService;
+
+    private final ShopTokenUtil shopTokenUtil;
+
+    private final RedisTemplate<String, String> redisTemplate;
+    @Override
+    public UserLoginVO login(UserLoginDTO loginDTO) {
+        PartnerUser user = null;
+        String email = loginDTO.getEmail();
+        String captchaKey = CaptchaKeyEnum.generateEmailCaptchaKey(
+                CaptchaTypeEnum.SUPPLIER_LOGIN.getType(), email);
+        String captcha = captchaService.getCaptcha(captchaKey, 5);
+        Assert.isTrue(StrUtil.equals(loginDTO.getCaptcha(), captcha), "验证码无效");
+        captchaService.clearCaptcha(captchaKey);
+        // 查询用户
+        LambdaQueryWrapper<PartnerUser> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(PartnerUser::getEmail, email);
+        user = this.getOne(queryWrapper);
+        if (user == null) {
+            // 创建新用户
+            user = new PartnerUser();
+            user.setId(String.valueOf(IdWorker.getId()));
+            user.setEmail(email);
+            user.setRegisterTime(DateUtil.date());
+            user.setState(1);
+            user.setLoginCount(1);
+            user.setLoginTime(DateUtil.date());
+            user.setBillNo(middleSequenceService.generate("KH", 6));
+            user.setInfoState(0);
+            user.setAuditState(0);
+            this.save(user);
+        } else {
+            // 更新登录信息
+            user.setLoginCount(user.getLoginCount() + 1);
+            user.setLoginTime(DateUtil.date());
+            this.updateById(user);
+        }
+        UserLoginVO vo = new UserLoginVO();
+        if (user.getState()==1) {
+            vo.setToken(shopTokenUtil.createPartnerCustomerUserToken(user.getId()));
+        }
+        UserInfoVO userInfoVO = new UserInfoVO();
+        BeanUtils.copyProperties(user, userInfoVO);
+        vo.setUserInfo(userInfoVO);
+        return vo;
+    }
+
+    @Override
+    public void logout(String token) {
+        if (StrUtil.isNotBlank(token)) {
+            long remainingSeconds = shopTokenUtil.getRemainingSeconds(token);
+            if (remainingSeconds > 0) {
+                redisTemplate.opsForValue().set("shop:user:invalid-token:" + token, "1", remainingSeconds, TimeUnit.SECONDS);
+            }
+        }
+    }
+
+    @Override
+    public UserInfoVO getUserInfo() {
+        PartnerUser customerUser = getCurrentLoginCustomerUser();
+        if (null == customerUser) {
+            throw new RuntimeException("获取用户信息失败");
+        }
+        UserInfoVO userInfoVO = new UserInfoVO();
+        BeanUtils.copyProperties(customerUser, userInfoVO);
+        return userInfoVO;
+    }
+
+    @Override
+    public void updateUserInfo(UserInfoDTO dto) {
+        PartnerUser updateCustomerUser = new PartnerUser();
+        BeanUtils.copyProperties(dto, updateCustomerUser);
+        updateCustomerUser.setId(CustomerUserHolder.currentLoginUserId());
+        baseMapper.updateById(updateCustomerUser);
+    }
+
+    private PartnerUser getCurrentLoginCustomerUser() {
+        try {
+            String userId = CustomerUserHolder.currentLoginUserId(); // 此方法也提供给其他模块调用,所以加一下登录和类型判断
+            PartnerUser customerUser = getById(userId);
+            return customerUser;
+        } catch (Exception e) {
+            return null;
+        }
+    }
+}

+ 51 - 0
jeecg-module-system/jeecg-shop/src/main/java/org/jeecg/modules/shop/user/util/CustomerUserHolder.java

@@ -0,0 +1,51 @@
+package org.jeecg.modules.shop.user.util;
+
+import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.StrUtil;
+import org.apache.shiro.SecurityUtils;
+import org.jeecg.common.constant.CommonConstant;
+import org.jeecg.common.system.vo.PartnerUserLoginUser;
+import org.jeecg.config.shiro.PartnerToken;
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import javax.servlet.http.HttpServletRequest;
+
+public abstract class CustomerUserHolder {
+    
+    /**
+     * 判断当前请求是否是登录状态,如果不是,判断是否可以登录
+     */
+    public static boolean isAuthenticatedOrIsCanLogin() {
+        if (SecurityUtils.getSubject().isAuthenticated()) {
+            return true;
+        }
+        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
+        if (null == requestAttributes) {
+            return false;
+        }
+        try {
+            HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
+            String token = request.getHeader(CommonConstant.X_ACCESS_TOKEN);
+            SecurityUtils.getSubject().login(new PartnerToken(token));
+            return true;
+        } catch (Exception e) {
+            return false;
+        }
+    }
+
+    public static String currentLoginUserId() {
+        if (!SecurityUtils.getSubject().isAuthenticated()) {
+           throw new RuntimeException("Token失效,请重新登录!");
+        }
+        Object principal = SecurityUtils.getSubject().getPrincipal();
+        if (!(principal instanceof PartnerUserLoginUser)) {
+            throw new RuntimeException("Token异常!");
+        }
+        String uid = ((PartnerUserLoginUser) principal).getId();
+        Assert.isTrue(StrUtil.isNotBlank(uid), "Token失效,请重新登录!");
+        return uid;
+    }
+    
+}

+ 5 - 0
jeecg-module-system/jeecg-system-biz/pom.xml

@@ -30,6 +30,11 @@
             <groupId>org.jeecgframework.boot</groupId>
             <artifactId>jeecg-boot-starter-rabbitmq</artifactId>
         </dependency>
+		<dependency>
+			<groupId>com.aliyun</groupId>
+			<artifactId>dysmsapi20170525</artifactId>
+			<version>3.0.0</version>
+		</dependency>
 	</dependencies>
 
 </project>

+ 31 - 0
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/sequence/entity/MiddleSequence.java

@@ -0,0 +1,31 @@
+package org.jeecg.modules.sequence.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+@Data
+@TableName("middle_sequence")
+@Accessors(chain = true)
+@EqualsAndHashCode(callSuper = false)
+@ApiModel(value = "middle_sequence对象", description = "序列号")
+public class MiddleSequence {
+
+    /** id */
+    @TableId(type = IdType.ASSIGN_ID)
+    @ApiModelProperty(value = "id")
+    private String id;
+
+    /** 前缀 */
+    @ApiModelProperty(value = "前缀")
+    private String prefix;
+
+    /** 顺序号 */
+    @ApiModelProperty(value = "顺序号")
+    private Integer seqCode;
+}

+ 14 - 0
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/sequence/mapper/MiddleSequenceMapper.java

@@ -0,0 +1,14 @@
+package org.jeecg.modules.sequence.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.jeecg.modules.sequence.entity.MiddleSequence;
+
+/**
+ * @Description: middle_attachment
+ * @Author: jeecg-boot
+ * @Date: 2024-04-26
+ * @Version: V1.0
+ */
+public interface MiddleSequenceMapper extends BaseMapper<MiddleSequence> {
+
+}

+ 5 - 0
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/sequence/mapper/xml/MiddleSequenceMapper.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.jeecg.modules.sequence.mapper.MiddleSequenceMapper">
+
+</mapper>

+ 24 - 0
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/sequence/service/IMiddleSequenceService.java

@@ -0,0 +1,24 @@
+package org.jeecg.modules.sequence.service;
+
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import org.jeecg.modules.sequence.entity.MiddleSequence;
+
+/**
+ * @Description: middle_sequence
+ * @Author: jeecg-boot
+ * @Date: 2024-04-26
+ * @Version: V1.0
+ */
+public interface IMiddleSequenceService extends IService<MiddleSequence> {
+
+    /**
+     * 生成序列号
+     *
+     * @param prefix 前缀
+     * @param length 长度
+     * @return
+     */
+    String generate(String prefix, Integer length);
+
+}

+ 53 - 0
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/sequence/service/impl/MiddleSequenceServiceImpl.java

@@ -0,0 +1,53 @@
+package org.jeecg.modules.sequence.service.impl;
+
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.IdWorker;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.extern.slf4j.Slf4j;
+import org.jeecg.modules.sequence.entity.MiddleSequence;
+import org.jeecg.modules.sequence.mapper.MiddleSequenceMapper;
+import org.jeecg.modules.sequence.service.IMiddleSequenceService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Propagation;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * @Description: middle_attachment
+ * @Author: jeecg-boot
+ * @Date: 2024-04-26
+ * @Version: V1.0
+ */
+@Slf4j
+@Service
+public class MiddleSequenceServiceImpl extends ServiceImpl<MiddleSequenceMapper, MiddleSequence> implements IMiddleSequenceService {
+
+    @Override
+    @Transactional(propagation = Propagation.NOT_SUPPORTED)
+    public String generate(String prefix, Integer length) {
+        synchronized (Thread.currentThread()) {
+            QueryWrapper<MiddleSequence> queryWrapper = new QueryWrapper<>();
+            queryWrapper.lambda().eq(MiddleSequence::getPrefix, prefix);
+            MiddleSequence middleSequence = baseMapper.selectOne(queryWrapper);
+
+            int seqCode = 1;
+            if (ObjectUtil.isEmpty(middleSequence)) {
+                middleSequence = new MiddleSequence();
+                middleSequence.setId(IdWorker.getIdStr());
+                middleSequence.setPrefix(prefix);
+                middleSequence.setSeqCode(seqCode);
+                baseMapper.insert(middleSequence);
+            } else {
+                seqCode = middleSequence.getSeqCode() + 1;
+                middleSequence.setSeqCode(seqCode);
+                baseMapper.updateById(middleSequence);
+            }
+
+            String sequence = prefix + StrUtil.fill(String.valueOf(seqCode), '0', length, true);
+            log.info(">>>> 生成序列号参数[{}],结果[{}]", prefix, sequence);
+            return sequence;
+        }
+    }
+
+}

+ 5 - 0
jeecg-module-system/jeecg-system-start/pom.xml

@@ -18,6 +18,11 @@
             <artifactId>jeecg-system-biz</artifactId>
             <version>${jeecgboot.version}</version>
         </dependency>
+        <dependency>
+            <groupId>org.jeecg</groupId>
+            <artifactId>jeecg-shop</artifactId>
+            <version>3.6.3</version>
+        </dependency>
     </dependencies>
 
     <build>

+ 23 - 0
jeecg-module-system/jeecg-system-start/src/main/resources/application-dev.yml

@@ -180,3 +180,26 @@ justauth:
     type: default
     prefix: 'demo::'
     timeout: 1h
+
+aliyun:
+  email:
+    host: "smtp.qiye.aliyun.com"
+    port: 465
+    ssl: true
+    from: "Ejet <it@ejet.com>"
+    user: "it@ejet.com"
+    token: "3Ihv511avNwh47I5"
+    limit:
+      single-of-hour: 5
+      single-of-day: 10
+      total-of-hour: 300
+      total-of-day: 5000
+  sms:
+    ak: "LTAI5tLFnm8N4yGrrC5biJqS"
+    sk: "vzhgZgDISHEBCXb8Y1gUUxAFUA4KdO"
+
+shop:
+  user:
+    partner:
+      token-key: "ocfMuJ6SHIbFTuZwbtvlJY8dprAeZg7hxXv4hwrUx2FzbDEV0DqlCeXeuSsseeFlvuT84zCumODHcSZA4XvEWIEu28Qqd89uBDqYiSwoi0pIazpLtPpeKDK8cltRXBxvUEMFIdwLtuAdLze3Xp2CS3zAxawCL713VKRUuhcvQ6q8aj73PoG9J9W6mfHicAaBBg1aDTbVSruBAi6HQ4vDR05TASBCfRoTLe3iV34WRea4Lc96FL9Km4MBGxkAX003"
+      token-expire: 24

+ 23 - 0
jeecg-module-system/jeecg-system-start/src/main/resources/application-prod.yml

@@ -180,3 +180,26 @@ justauth:
     type: default
     prefix: 'demo::'
     timeout: 1h
+
+aliyun:
+  email:
+    host: "smtp.qiye.aliyun.com"
+    port: 465
+    ssl: true
+    from: "Ejet <it@ejet.com>"
+    user: "it@ejet.com"
+    token: "3Ihv511avNwh47I5"
+    limit:
+      single-of-hour: 5
+      single-of-day: 10
+      total-of-hour: 300
+      total-of-day: 5000
+  sms:
+    ak: "LTAI5tLFnm8N4yGrrC5biJqS"
+    sk: "vzhgZgDISHEBCXb8Y1gUUxAFUA4KdO"
+
+shop:
+  user:
+    partner:
+      token-key: "ocfMuJ6SHIbFTuZwbtvlJY8dprAeZg7hxXv4hwrUx2FzbDEV0DqlCeXeuSsseeFlvuT84zCumODHcSZA4XvEWIEu28Qqd89uBDqYiSwoi0pIazpLtPpeKDK8cltRXBxvUEMFIdwLtuAdLze3Xp2CS3zAxawCL713VKRUuhcvQ6q8aj73PoG9J9W6mfHicAaBBg1aDTbVSruBAi6HQ4vDR05TASBCfRoTLe3iV34WRea4Lc96FL9Km4MBGxkAX004"
+      token-expire: 24

+ 1 - 0
jeecg-module-system/pom.xml

@@ -13,6 +13,7 @@
     <packaging>pom</packaging>
 
     <modules>
+        <module>jeecg-shop</module>
         <module>jeecg-system-api</module>
         <module>jeecg-system-biz</module>
         <module>jeecg-system-start</module>