Преглед изворни кода

更改短信验证码校验 增加图片验证码

ssk пре 1 месец
родитељ
комит
7a54cccfea

+ 3 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/LifeUserVo.java

@@ -59,4 +59,7 @@ public class LifeUserVo extends LifeUser {
 
     @ApiModelProperty(value = "简介")
     private String blurb;
+
+    @ApiModelProperty(value = "验证码")
+    private String verificationCode;
 }

+ 3 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/StoreUserVo.java

@@ -36,4 +36,7 @@ public class StoreUserVo extends StoreUser {
 
     @ApiModelProperty(value = "倒计时")
     private long countdown;
+
+    @ApiModelProperty(value = "手机号")
+    private String phone;
 }

+ 7 - 0
alien-gateway/pom.xml

@@ -103,6 +103,13 @@
             <artifactId>fastjson</artifactId>
         </dependency>
 
+        <!-- 验证码 -->
+        <dependency>
+            <groupId>pro.fessional</groupId>
+            <artifactId>kaptcha</artifactId>
+        </dependency>
+
+
         <!--Swagger Start-->
         <dependency>
             <groupId>io.springfox</groupId>

+ 57 - 0
alien-gateway/src/main/java/shop/alien/gateway/config/BeanConfig.java

@@ -0,0 +1,57 @@
+package shop.alien.gateway.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;
+
+import static com.google.code.kaptcha.Constants.*;
+
+/**
+ * @author ssk
+ * @version 1.0
+ * @date 2025/10/31 14:46
+ */
+@Configuration
+public class BeanConfig {
+
+    @Bean(name = "captchaProducerMath")
+    public DefaultKaptcha getKaptchaBeanMath() {
+        DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
+        Properties properties = new Properties();
+        // 是否有边框 默认为true 我们可以自己设置yes,no
+        properties.setProperty(KAPTCHA_BORDER, "yes");
+        // 边框颜色 默认为Color.BLACK
+        properties.setProperty(KAPTCHA_BORDER_COLOR, "105,179,90");
+        // 验证码文本字符颜色 默认为Color.BLACK
+        properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "blue");
+        // 验证码图片宽度 默认为200
+        properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160");
+        // 验证码图片高度 默认为50
+        properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60");
+        // 验证码文本字符大小 默认为40
+        properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "35");
+        // KAPTCHA_SESSION_KEY
+        properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCodeMath");
+        // 验证码文本生成器 使用默认实现
+        properties.setProperty(KAPTCHA_TEXTPRODUCER_IMPL, "shop.alien.gateway.util.KaptchaTextCreator");
+        // 验证码文本字符间距 默认为2
+        properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_SPACE, "3");
+        // 验证码文本字符长度 默认为5
+        properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "6");
+        // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)
+        properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier");
+        // 验证码噪点颜色 默认为Color.BLACK
+        properties.setProperty(KAPTCHA_NOISE_COLOR, "white");
+        // 干扰实现类
+        properties.setProperty(KAPTCHA_NOISE_IMPL, "com.google.code.kaptcha.impl.NoNoise");
+        // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy
+        properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy");
+        Config config = new Config(properties);
+        defaultKaptcha.setConfig(config);
+        return defaultKaptcha;
+    }
+
+}

+ 70 - 0
alien-gateway/src/main/java/shop/alien/gateway/controller/CaptchaImageController.java

@@ -0,0 +1,70 @@
+package shop.alien.gateway.controller;
+
+import com.google.code.kaptcha.Producer;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.util.FastByteArrayOutputStream;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import reactor.core.publisher.Mono;
+import shop.alien.entity.result.R;
+import shop.alien.gateway.config.BaseRedisService;
+import shop.alien.gateway.util.Base64;
+
+import javax.annotation.Resource;
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * @author ssk
+ * @version 1.0
+ * @date 2025/10/31 14:12
+ */
+@RestController
+@RequestMapping("/captcha")
+public class CaptchaImageController {
+
+    @Resource(name = "captchaProducerMath")
+    private Producer captchaProducerMath;
+
+    @Autowired
+    private BaseRedisService redisCache;
+
+    /**
+     * 5分钟
+     */
+    private Long CAPTCHA_EXPIRATION = 5 * 60 * 1000L;
+
+    /**
+     * 生成验证码
+     */
+    @GetMapping("/captchaImage")
+    public Mono<R<Map<String, String>>> getCode() {
+        Map<String, String> resultMap = new HashMap<>();
+        // 保存验证码信息
+        String uuid = UUID.randomUUID().toString();
+        String verifyKey = "captcha_codes:" + uuid;
+        String capStr, code;
+        BufferedImage image;
+        String capText = captchaProducerMath.createText();
+        capStr = capText.substring(0, capText.lastIndexOf("@"));
+        code = capText.substring(capText.lastIndexOf("@") + 1);
+        image = captchaProducerMath.createImage(capStr);
+        redisCache.setString(verifyKey, code, CAPTCHA_EXPIRATION);
+        // 转换流信息写出
+        FastByteArrayOutputStream os = new FastByteArrayOutputStream();
+        try {
+            ImageIO.write(image, "jpg", os);
+        } catch (IOException e) {
+            return Mono.just(R.fail("验证码生成失败"));
+        }
+        resultMap.put("uuid", uuid);
+        resultMap.put("img", "data:image/gif;base64," + Base64.encode(os.toByteArray()));
+        return Mono.just(R.data(resultMap));
+    }
+
+}

+ 20 - 5
alien-gateway/src/main/java/shop/alien/gateway/controller/LifeUserController.java

@@ -6,6 +6,7 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.web.bind.annotation.*;
 import shop.alien.entity.result.R;
 import shop.alien.entity.store.vo.LifeUserVo;
+import shop.alien.gateway.config.BaseRedisService;
 import shop.alien.gateway.service.LifeUserService;
 
 /**
@@ -19,15 +20,29 @@ import shop.alien.gateway.service.LifeUserService;
 @RequiredArgsConstructor
 public class LifeUserController {
 
-    private final LifeUserService service;
+    private final LifeUserService lifeUserService;
+
+    private final BaseRedisService baseRedisService;
 
     @ApiOperation("用户登录")
     @ApiOperationSupport(order = 1)
-    @ApiImplicitParams({@ApiImplicitParam(name = "phoneNum", value = "手机号", dataType = "String", paramType = "query", required = true)})
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "phoneNum", value = "手机号", dataType = "String", paramType = "query", required = true),
+            @ApiImplicitParam(name = "code", value = "验证码", dataType = "String", paramType = "query", required = true)
+    })
     @GetMapping("/userLogin")
-    public R<LifeUserVo> userLogin(@RequestParam("phoneNum") String phoneNum) {
-        log.info("LifeUserController.userLogin?phoneNum={}", phoneNum);
-        LifeUserVo userVo = service.userLogin(phoneNum);
+    public R<LifeUserVo> userLogin(@RequestParam("phoneNum") String phoneNum,
+                                   @RequestParam("code") String code) {
+        log.info("LifeUserController.userLogin?phoneNum={}&code={}", phoneNum, code);
+        // 2025-11-04 验证码-用户端登录
+        String cacheCode = baseRedisService.getString("verification_user_login_" + phoneNum);
+        if (null == cacheCode) {
+            return R.fail("当验证码过期或未发送");
+        }
+        if (!cacheCode.trim().equals(code.trim())) {
+            return R.fail("验证码错误");
+        }
+        LifeUserVo userVo = lifeUserService.userLogin(phoneNum);
         if (null == userVo) {
             return R.fail("登录失败");
         }

+ 25 - 5
alien-gateway/src/main/java/shop/alien/gateway/controller/StoreUserController.java

@@ -8,6 +8,7 @@ import org.springframework.web.bind.annotation.*;
 import shop.alien.entity.result.R;
 import shop.alien.entity.store.StoreUser;
 import shop.alien.entity.store.vo.StoreUserVo;
+import shop.alien.gateway.config.BaseRedisService;
 import shop.alien.gateway.mapper.StoreUserMapper;
 import shop.alien.gateway.service.StoreUserService;
 
@@ -30,17 +31,36 @@ import java.util.Optional;
 public class StoreUserController {
 
     private final StoreUserService storeUserService;
+
     private final StoreUserMapper storeUserMapper;
 
+    private final BaseRedisService baseRedisService;
+
     @ApiOperation("门店用户登录")
     @ApiOperationSupport(order = 1)
-    @ApiImplicitParams({@ApiImplicitParam(name = "phone", value = "手机号", dataType = "String", paramType = "query", required = true),
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "phone", value = "手机号", dataType = "String", paramType = "query", required = true),
             @ApiImplicitParam(name = "password", value = "密码", dataType = "String", paramType = "query", required = true),
-            @ApiImplicitParam(name = "isPassword", value = "是否密码登录", dataType = "Boolean", paramType = "query", required = true)})
+            @ApiImplicitParam(name = "isPassword", value = "是否密码登录", dataType = "Boolean", paramType = "query", required = true),
+            @ApiImplicitParam(name = "code", value = "验证码", dataType = "String", paramType = "query", required = true)
+    })
     @GetMapping("/login")
-    public R<StoreUserVo> login(String phone, String password,
-                                @RequestParam(defaultValue = "true", required = false) Boolean isPassword) {
-        log.info("StoreUserController.login?phone={}&password={}&isPassword={}", phone, password, isPassword);
+    public R<StoreUserVo> login(
+            @RequestParam("phone") String phone,
+            @RequestParam("password") String password,
+            @RequestParam("isPassword") Boolean isPassword,
+            @RequestParam("code") String code) {
+        log.info("StoreUserController.login?phone={}&password={}&isPassword={}&code={}", phone, password, isPassword, code);
+        if (!isPassword) {
+            // 2025-11-04 验证码-商户端登录
+            String cacheCode = baseRedisService.getString("verification_store_login_" + phone);
+            if (null == cacheCode) {
+                return R.fail("验证码过期或未发送");
+            }
+            if (!cacheCode.trim().equals(code.trim())) {
+                return R.fail("验证码错误");
+            }
+        }
         StoreUser storeUser = storeUserMapper.selectOne(new LambdaQueryWrapper<StoreUser>()
                 .eq(StoreUser::getPhone, phone));
         if (null == storeUser) {

+ 253 - 0
alien-gateway/src/main/java/shop/alien/gateway/util/Base64.java

@@ -0,0 +1,253 @@
+package shop.alien.gateway.util;
+
+/**
+ * Base64工具类
+ *
+ * @author ssk
+ */
+public final class Base64 {
+    static private final int BASELENGTH = 128;
+    static private final int LOOKUPLENGTH = 64;
+    static private final int TWENTYFOURBITGROUP = 24;
+    static private final int EIGHTBIT = 8;
+    static private final int SIXTEENBIT = 16;
+    static private final int FOURBYTE = 4;
+    static private final int SIGN = -128;
+    static private final char PAD = '=';
+    static final private byte[] base64Alphabet = new byte[BASELENGTH];
+    static final private char[] lookUpBase64Alphabet = new char[LOOKUPLENGTH];
+
+    static {
+        for (int i = 0; i < BASELENGTH; ++i) {
+            base64Alphabet[i] = -1;
+        }
+        for (int i = 'Z'; i >= 'A'; i--) {
+            base64Alphabet[i] = (byte) (i - 'A');
+        }
+        for (int i = 'z'; i >= 'a'; i--) {
+            base64Alphabet[i] = (byte) (i - 'a' + 26);
+        }
+
+        for (int i = '9'; i >= '0'; i--) {
+            base64Alphabet[i] = (byte) (i - '0' + 52);
+        }
+
+        base64Alphabet['+'] = 62;
+        base64Alphabet['/'] = 63;
+
+        for (int i = 0; i <= 25; i++) {
+            lookUpBase64Alphabet[i] = (char) ('A' + i);
+        }
+
+        for (int i = 26, j = 0; i <= 51; i++, j++) {
+            lookUpBase64Alphabet[i] = (char) ('a' + j);
+        }
+
+        for (int i = 52, j = 0; i <= 61; i++, j++) {
+            lookUpBase64Alphabet[i] = (char) ('0' + j);
+        }
+        lookUpBase64Alphabet[62] = (char) '+';
+        lookUpBase64Alphabet[63] = (char) '/';
+    }
+
+    private static boolean isWhiteSpace(char octect) {
+        return (octect == 0x20 || octect == 0xd || octect == 0xa || octect == 0x9);
+    }
+
+    private static boolean isPad(char octect) {
+        return (octect == PAD);
+    }
+
+    private static boolean isData(char octect) {
+        return (octect < BASELENGTH && base64Alphabet[octect] != -1);
+    }
+
+    /**
+     * Encodes hex octects into Base64
+     *
+     * @param binaryData Array containing binaryData
+     * @return Encoded Base64 array
+     */
+    public static String encode(byte[] binaryData) {
+        if (binaryData == null) {
+            return null;
+        }
+
+        int lengthDataBits = binaryData.length * EIGHTBIT;
+        if (lengthDataBits == 0) {
+            return "";
+        }
+
+        int fewerThan24bits = lengthDataBits % TWENTYFOURBITGROUP;
+        int numberTriplets = lengthDataBits / TWENTYFOURBITGROUP;
+        int numberQuartet = fewerThan24bits != 0 ? numberTriplets + 1 : numberTriplets;
+        char encodedData[] = null;
+
+        encodedData = new char[numberQuartet * 4];
+
+        byte k = 0, l = 0, b1 = 0, b2 = 0, b3 = 0;
+
+        int encodedIndex = 0;
+        int dataIndex = 0;
+
+        for (int i = 0; i < numberTriplets; i++) {
+            b1 = binaryData[dataIndex++];
+            b2 = binaryData[dataIndex++];
+            b3 = binaryData[dataIndex++];
+
+            l = (byte) (b2 & 0x0f);
+            k = (byte) (b1 & 0x03);
+
+            byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
+            byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0);
+            byte val3 = ((b3 & SIGN) == 0) ? (byte) (b3 >> 6) : (byte) ((b3) >> 6 ^ 0xfc);
+
+            encodedData[encodedIndex++] = lookUpBase64Alphabet[val1];
+            encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k << 4)];
+            encodedData[encodedIndex++] = lookUpBase64Alphabet[(l << 2) | val3];
+            encodedData[encodedIndex++] = lookUpBase64Alphabet[b3 & 0x3f];
+        }
+
+        // form integral number of 6-bit groups
+        if (fewerThan24bits == EIGHTBIT) {
+            b1 = binaryData[dataIndex];
+            k = (byte) (b1 & 0x03);
+            byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
+            encodedData[encodedIndex++] = lookUpBase64Alphabet[val1];
+            encodedData[encodedIndex++] = lookUpBase64Alphabet[k << 4];
+            encodedData[encodedIndex++] = PAD;
+            encodedData[encodedIndex++] = PAD;
+        } else if (fewerThan24bits == SIXTEENBIT) {
+            b1 = binaryData[dataIndex];
+            b2 = binaryData[dataIndex + 1];
+            l = (byte) (b2 & 0x0f);
+            k = (byte) (b1 & 0x03);
+
+            byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
+            byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0);
+
+            encodedData[encodedIndex++] = lookUpBase64Alphabet[val1];
+            encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k << 4)];
+            encodedData[encodedIndex++] = lookUpBase64Alphabet[l << 2];
+            encodedData[encodedIndex++] = PAD;
+        }
+        return new String(encodedData);
+    }
+
+    /**
+     * Decodes Base64 data into octects
+     *
+     * @param encoded string containing Base64 data
+     * @return Array containind decoded data.
+     */
+    public static byte[] decode(String encoded) {
+        if (encoded == null) {
+            return null;
+        }
+
+        char[] base64Data = encoded.toCharArray();
+        // remove white spaces
+        int len = removeWhiteSpace(base64Data);
+
+        if (len % FOURBYTE != 0) {
+            return null;// should be divisible by four
+        }
+
+        int numberQuadruple = (len / FOURBYTE);
+
+        if (numberQuadruple == 0) {
+            return new byte[0];
+        }
+
+        byte decodedData[] = null;
+        byte b1 = 0, b2 = 0, b3 = 0, b4 = 0;
+        char d1 = 0, d2 = 0, d3 = 0, d4 = 0;
+
+        int i = 0;
+        int encodedIndex = 0;
+        int dataIndex = 0;
+        decodedData = new byte[(numberQuadruple) * 3];
+
+        for (; i < numberQuadruple - 1; i++) {
+
+            if (!isData((d1 = base64Data[dataIndex++])) || !isData((d2 = base64Data[dataIndex++]))
+                    || !isData((d3 = base64Data[dataIndex++])) || !isData((d4 = base64Data[dataIndex++]))) {
+                return null;
+            } // if found "no data" just return null
+
+            b1 = base64Alphabet[d1];
+            b2 = base64Alphabet[d2];
+            b3 = base64Alphabet[d3];
+            b4 = base64Alphabet[d4];
+
+            decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4);
+            decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
+            decodedData[encodedIndex++] = (byte) (b3 << 6 | b4);
+        }
+
+        if (!isData((d1 = base64Data[dataIndex++])) || !isData((d2 = base64Data[dataIndex++]))) {
+            return null;// if found "no data" just return null
+        }
+
+        b1 = base64Alphabet[d1];
+        b2 = base64Alphabet[d2];
+
+        d3 = base64Data[dataIndex++];
+        d4 = base64Data[dataIndex++];
+        if (!isData((d3)) || !isData((d4))) {// Check if they are PAD characters
+            if (isPad(d3) && isPad(d4)) {
+                if ((b2 & 0xf) != 0)// last 4 bits should be zero
+                {
+                    return null;
+                }
+                byte[] tmp = new byte[i * 3 + 1];
+                System.arraycopy(decodedData, 0, tmp, 0, i * 3);
+                tmp[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
+                return tmp;
+            } else if (!isPad(d3) && isPad(d4)) {
+                b3 = base64Alphabet[d3];
+                if ((b3 & 0x3) != 0)// last 2 bits should be zero
+                {
+                    return null;
+                }
+                byte[] tmp = new byte[i * 3 + 2];
+                System.arraycopy(decodedData, 0, tmp, 0, i * 3);
+                tmp[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4);
+                tmp[encodedIndex] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
+                return tmp;
+            } else {
+                return null;
+            }
+        } else { // No PAD e.g 3cQl
+            b3 = base64Alphabet[d3];
+            b4 = base64Alphabet[d4];
+            decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4);
+            decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
+            decodedData[encodedIndex++] = (byte) (b3 << 6 | b4);
+
+        }
+        return decodedData;
+    }
+
+    /**
+     * remove WhiteSpace from MIME containing encoded Base64 data.
+     *
+     * @param data the byte array of base64 data (with WS)
+     * @return the new length
+     */
+    private static int removeWhiteSpace(char[] data) {
+        if (data == null) {
+            return 0;
+        }
+
+        // count characters that's not whitespace
+        int newSize = 0;
+        int len = data.length;
+        for (int i = 0; i < len; i++) {
+            if (!isWhiteSpace(data[i])) {
+                data[newSize++] = data[i];
+            }
+        }
+        return newSize;
+    }
+}

+ 56 - 0
alien-gateway/src/main/java/shop/alien/gateway/util/KaptchaTextCreator.java

@@ -0,0 +1,56 @@
+package shop.alien.gateway.util;
+
+import com.google.code.kaptcha.text.impl.DefaultTextCreator;
+
+import java.util.Random;
+
+/**
+ * 验证码文本生成器
+ *
+ * @author ruoyi
+ */
+public class KaptchaTextCreator extends DefaultTextCreator {
+    private static final String[] CNUMBERS = "0,1,2,3,4,5,6,7,8,9,10".split(",");
+
+    @Override
+    public String getText() {
+        Integer result = 0;
+        Random random = new Random();
+        int x = random.nextInt(10);
+        int y = random.nextInt(10);
+        StringBuilder suChinese = new StringBuilder();
+        int randomoperands = random.nextInt(3);
+        if (randomoperands == 0) {
+            result = x * y;
+            suChinese.append(CNUMBERS[x]);
+            suChinese.append("*");
+            suChinese.append(CNUMBERS[y]);
+        } else if (randomoperands == 1) {
+            if ((x != 0) && y % x == 0) {
+                result = y / x;
+                suChinese.append(CNUMBERS[y]);
+                suChinese.append("/");
+                suChinese.append(CNUMBERS[x]);
+            } else {
+                result = x + y;
+                suChinese.append(CNUMBERS[x]);
+                suChinese.append("+");
+                suChinese.append(CNUMBERS[y]);
+            }
+        } else {
+            if (x >= y) {
+                result = x - y;
+                suChinese.append(CNUMBERS[x]);
+                suChinese.append("-");
+                suChinese.append(CNUMBERS[y]);
+            } else {
+                result = y - x;
+                suChinese.append(CNUMBERS[y]);
+                suChinese.append("-");
+                suChinese.append(CNUMBERS[x]);
+            }
+        }
+        suChinese.append("=?@" + result);
+        return suChinese.toString();
+    }
+}

+ 20 - 8
alien-store/src/main/java/shop/alien/store/controller/AliController.java

@@ -8,6 +8,7 @@ import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
 import shop.alien.entity.result.R;
 import shop.alien.entity.store.StoreAliPayLog;
+import shop.alien.store.config.BaseRedisService;
 import shop.alien.store.service.AliService;
 import shop.alien.store.util.ali.AliApi;
 import shop.alien.store.util.ali.AliSms;
@@ -45,8 +46,11 @@ public class AliController {
     private final AlipayTradeRefund alipayTradeRefund;
 
     private final AliOSSUtil aliOSSUtil;
+
     private final AliApi aliApi;
 
+    private final BaseRedisService baseRedisService;
+
     @ApiOperation("阿里回调")
     @ApiOperationSupport(order = 1)
     @GetMapping("/notify")
@@ -88,13 +92,21 @@ public class AliController {
 
     @ApiOperation("发送短信")
     @ApiOperationSupport(order = 4)
-    @ApiImplicitParams({@ApiImplicitParam(name = "phone", value = "手机号", dataType = "String", paramType = "query", required = true)})
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "phone", value = "手机号", dataType = "String", paramType = "query", required = true),
+            @ApiImplicitParam(name = "appType", value = "端区分(0:用户, 1:商家)", dataType = "Integer", paramType = "query", required = true, defaultValue = "0"),
+            @ApiImplicitParam(name = "businessType", value = "业务类型 (0:登录, 1:修改密码, 2:注册, 3:修改手机号, 4:注销店铺, 5:注销账号, 6:忘记密码)", dataType = "Integer", paramType = "query", required = true, defaultValue = "0")
+    })
     @GetMapping("/sendSms")
-    public R sendSms(String phone) {
-        Integer code = aliSmsConfig.sendSms(phone);
-        log.info("AliController.sendSms?phone={}&code={}", phone, code);
+    public R sendSms(
+            @RequestParam("phone") String phone,
+            @RequestParam("appType") Integer appType,
+            @RequestParam("businessType") Integer businessType
+    ) {
+        Integer code = aliSmsConfig.sendSms(phone, appType, businessType);
+        log.info("AliController.sendSms?phone={}&code={}&businessType={}", phone, code, businessType);
         if (code != null) {
-            return R.data(code);
+            return R.data("短信发送成功");
         }
         return R.fail("短信发送失败");
     }
@@ -224,9 +236,9 @@ public class AliController {
     })
     @GetMapping("/processRefund")
     public String processRefund(@RequestParam(value = "outTradeNo") String outTradeNo,
-                                       @RequestParam(value = "refundAmount") String refundAmount,
-                                       @RequestParam(value = "refundReason") String refundReason,
-                                       @RequestParam(value = "partialRefundCode") String partialRefundCode) {
+                                @RequestParam(value = "refundAmount") String refundAmount,
+                                @RequestParam(value = "refundReason") String refundReason,
+                                @RequestParam(value = "partialRefundCode") String partialRefundCode) {
         return aliApi.processRefund(outTradeNo, refundAmount, refundReason, partialRefundCode);
     }
 }

+ 14 - 4
alien-store/src/main/java/shop/alien/store/controller/LifeUserController.java

@@ -12,6 +12,7 @@ import shop.alien.entity.store.LifeFans;
 import shop.alien.entity.store.LifeUser;
 import shop.alien.entity.store.vo.LifeUserVo;
 import shop.alien.mapper.LifeUserMapper;
+import shop.alien.store.config.BaseRedisService;
 import shop.alien.store.service.LifeUserService;
 import shop.alien.util.common.FileUpload;
 import shop.alien.util.common.RandomCreateUtil;
@@ -38,13 +39,14 @@ public class LifeUserController {
 
     private final LifeUserMapper lifeUserMapper;
 
+    private final BaseRedisService baseRedisService;
 
     @ApiOperation("用户登录")
     @ApiOperationSupport(order = 1)
     @ApiImplicitParams({@ApiImplicitParam(name = "phoneNum", value = "手机号", dataType = "String", paramType = "query", required = true),
-                        @ApiImplicitParam(name = "inviteCode", value = "邀请码", dataType = "String", paramType = "query", required = false)})
+            @ApiImplicitParam(name = "inviteCode", value = "邀请码", dataType = "String", paramType = "query", required = false)})
     @GetMapping("/userLogin")
-    public R<LifeUserVo> userLogin(@RequestParam("phoneNum") String phoneNum,  @RequestParam(required = false) String inviteCode) {
+    public R<LifeUserVo> userLogin(@RequestParam("phoneNum") String phoneNum, @RequestParam(required = false) String inviteCode) {
         log.info("LifeUserController.userLogin?phoneNum={}", phoneNum);
         LifeUserVo userVo = service.userLogin(phoneNum, inviteCode);
         if (null == userVo) {
@@ -170,8 +172,16 @@ public class LifeUserController {
      */
     @ApiOperation("用户端注销用户")
     @PostMapping("/liftCancelAccount")
-    public R<Boolean> liftCancelAccount(@RequestBody LifeUser user) {
-        log.info("StoreUserController.liftCancelAccount?LifeUser={}", user);
+    public R<Boolean> liftCancelAccount(@RequestBody LifeUserVo user) {
+        log.info("StoreUserController.liftCancelAccount?LifeUserVo={}", user.toString());
+        // 2025-11-04 验证码-用户端注销账号
+        String cacheCode = baseRedisService.getString("verification_user_cancel_account_" + user.getUserPhone());
+        if (null == cacheCode) {
+            return R.fail("验证码过期或未发送");
+        }
+        if (!cacheCode.trim().equals(user.getVerificationCode().trim())) {
+            return R.fail("验证码错误");
+        }
         service.liftCancelAccount(user);
         return R.success("注销成功");
     }

+ 20 - 14
alien-store/src/main/java/shop/alien/store/controller/StoreInfoController.java

@@ -6,16 +6,15 @@ import io.swagger.annotations.*;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.http.ResponseEntity;
-import org.springframework.util.CollectionUtils;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartRequest;
 import shop.alien.entity.result.R;
 import shop.alien.entity.store.*;
 import shop.alien.entity.store.dto.StoreInfoDto;
 import shop.alien.entity.store.vo.*;
-import shop.alien.mapper.StoreImgMapper;
 import shop.alien.mapper.TagsMainMapper;
 import shop.alien.mapper.WebAuditMapper;
+import shop.alien.store.config.BaseRedisService;
 import shop.alien.store.service.StoreInfoService;
 
 import java.io.IOException;
@@ -40,12 +39,12 @@ public class StoreInfoController {
 
     private final StoreInfoService storeInfoService;
 
-    private final StoreImgMapper storeImgMapper;
-
     private final TagsMainMapper tagsMainMapper;
 
     private final WebAuditMapper webAuditMapper;
 
+    private final BaseRedisService baseRedisService;
+
     @ApiOperation("获取所有门店")
     @ApiOperationSupport(order = 1)
     @GetMapping("/getAll")
@@ -68,9 +67,9 @@ public class StoreInfoController {
     @PostMapping("/saveOrUpdate")
     public R<StoreInfo> saveOrUpdate(@RequestBody StoreInfoDto storeInfo) {
         log.info("StoreInfoController.saveOrUpdate?storeInfo={}", storeInfo);
-        try{
+        try {
             int num = storeInfoService.saveOrUpdateStoreInfo(storeInfo);
-            if (num>0) {
+            if (num > 0) {
                 return R.success("成功");
             }
             return R.fail("失败");
@@ -241,8 +240,7 @@ public class StoreInfoController {
             @RequestParam(required = false) String weidu,
             @RequestParam(required = false) String renewContractStatus,
             @RequestParam(required = false) String foodLicenceStatus,
-            @RequestParam(required = false) String foodLicenceWhetherExpiredStatus)
-    {
+            @RequestParam(required = false) String foodLicenceWhetherExpiredStatus) {
         log.info("StoreInfoController.getStoresPage?pageNum={},pageSize={},storeName={},storeContact={},id={},storePhone={},storeType={},expiredState={},storeApplicationStatus={},storeStatus={},businessSection={},jingdu={},weidu={},renewContractStatus={},foodLicenceStatus={},foodLicenceWhetherExpiredStatus={}",
                 pageNum, pageSize, storeName, storeContact, id, storePhone, storeType, expiredState, storeApplicationStatus, storeStatus, businessSection, jingdu, weidu, renewContractStatus, foodLicenceStatus, foodLicenceWhetherExpiredStatus);
         return R.data(storeInfoService.getStorePage(pageNum, pageSize, storeName, storeContact, id, storePhone, storeType, expiredState, storeApplicationStatus, storeStatus, businessSection, jingdu, weidu, renewContractStatus, foodLicenceStatus, foodLicenceWhetherExpiredStatus));
@@ -424,6 +422,14 @@ public class StoreInfoController {
     @PostMapping("/logoutStore")
     public R logoutStore(@RequestBody StoreInfoVo storeInfo) {
         log.info("StoreInfoController.logoutStore?storeInfo={}", storeInfo);
+        // 2025-11-04 验证码-商家端注销店铺
+        String cacheCode = baseRedisService.getString("verification_store_cancel_store_" + storeInfo.getStorePhone());
+        if (null == cacheCode) {
+            return R.fail("当验证码过期或未发送");
+        }
+        if (!cacheCode.trim().equals(storeInfo.getVerificationCode().trim())) {
+            return R.fail("验证码错误");
+        }
         try {
             storeInfoService.logoutStore(storeInfo);
         } catch (Exception e) {
@@ -699,8 +705,8 @@ public class StoreInfoController {
                 boolean flag = storeInfoService.updateById(storeInfo);
                 if (flag) {
                     //待审核状态变为已审核
-                    WebAudit webAudit = webAuditMapper.selectOne(new LambdaQueryWrapper<WebAudit>().eq(WebAudit::getStoreInfoId,storeInfo.getId()).eq(WebAudit::getDeleteFlag,0).eq(WebAudit::getType,"7"));
-                    if(webAudit != null){
+                    WebAudit webAudit = webAuditMapper.selectOne(new LambdaQueryWrapper<WebAudit>().eq(WebAudit::getStoreInfoId, storeInfo.getId()).eq(WebAudit::getDeleteFlag, 0).eq(WebAudit::getType, "7"));
+                    if (webAudit != null) {
                         webAudit.setStatus("1");
                         webAuditMapper.updateById(webAudit);
                     }
@@ -714,16 +720,16 @@ public class StoreInfoController {
             boolean flag = storeInfoService.updateById(storeInfo);
             if (flag) {
                 int num = storeInfoService.foodLicenceType(storeInfoDto.getId());
-                if(num > 0){
+                if (num > 0) {
                     //待审核状态变为已审核
-                    WebAudit webAudit = webAuditMapper.selectOne(new LambdaQueryWrapper<WebAudit>().eq(WebAudit::getStoreInfoId,storeInfo.getId()).eq(WebAudit::getDeleteFlag,0).eq(WebAudit::getType,"7"));
-                    if(webAudit != null){
+                    WebAudit webAudit = webAuditMapper.selectOne(new LambdaQueryWrapper<WebAudit>().eq(WebAudit::getStoreInfoId, storeInfo.getId()).eq(WebAudit::getDeleteFlag, 0).eq(WebAudit::getType, "7"));
+                    if (webAudit != null) {
                         webAudit.setStatus("1");
                         webAuditMapper.updateById(webAudit);
                     }
                 }
                 return R.success("审核通过成功");
-            }else{
+            } else {
                 return R.fail("审核失败");
             }
         }

+ 72 - 91
alien-store/src/main/java/shop/alien/store/controller/StoreUserController.java

@@ -1,7 +1,6 @@
 package shop.alien.store.controller;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import io.swagger.annotations.*;
@@ -15,13 +14,12 @@ import shop.alien.entity.store.vo.StoreInfoVo;
 import shop.alien.entity.store.vo.StoreUserVo;
 import shop.alien.mapper.StoreImgMapper;
 import shop.alien.mapper.StoreUserMapper;
+import shop.alien.store.config.BaseRedisService;
 import shop.alien.store.service.StoreInfoService;
 import shop.alien.store.service.StoreUserService;
 
 import java.io.IOException;
 import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
 
 /**
  * 门店用户 前端控制器
@@ -39,31 +37,14 @@ import java.util.Optional;
 public class StoreUserController {
 
     private final StoreUserService storeUserService;
+
     private final StoreUserMapper storeUserMapper;
+
     private final StoreInfoService storeInfoService;
+
     private final StoreImgMapper storeImgMapper;
 
-    @ApiOperation("门店用户登录")
-    @ApiOperationSupport(order = 1)
-    @ApiImplicitParams({@ApiImplicitParam(name = "phone", value = "手机号", dataType = "String", paramType = "query", required = true),
-            @ApiImplicitParam(name = "password", value = "密码", dataType = "String", paramType = "query", required = true),
-            @ApiImplicitParam(name = "isPassword", value = "是否密码登录", dataType = "Boolean", paramType = "query", required = true)})
-    @GetMapping("/login")
-    public R<StoreUserVo> login(String phone, String password,
-                                @RequestParam(defaultValue = "true", required = false) Boolean isPassword) {
-        log.info("StoreUserController.login?phone={}&password={}&isPassword={}", phone, password, isPassword);
-        StoreUser storeUser = storeUserMapper.selectOne(new LambdaQueryWrapper<StoreUser>()
-                .eq(StoreUser::getPhone, phone));
-        if (null == storeUser) {
-            return R.fail("当前账号不存在,请先去注册账号!");
-        }
-        if (storeUser.getStatus() == 1) {
-            return R.fail("账号被禁用");
-        }
-        return Optional.ofNullable(storeUser).
-                map(user -> isPassword ? checkPassword(user, password) : storeUserService.createToKen(user)).
-                orElseGet(() -> R.fail("手机号不存在"));
-    }
+    private final BaseRedisService baseRedisService;
 
     /**
      * 校验商户端账号是否禁用
@@ -76,46 +57,52 @@ public class StoreUserController {
     @ApiImplicitParams({@ApiImplicitParam(name = "accountId", value = "账号id", dataType = "Integer", paramType = "query", required = true)})
     @GetMapping("/checkAccount")
     private R<StoreUserVo> checkAccount(Integer accountId) {
-        return storeUserService.getById(accountId).getStatus() != 1 ? R.success("账号正常")
-                : R.fail("账号禁用");
-    }
-
-    /**
-     * checkPwd
-     *
-     * @param user
-     * @param password
-     * @return
-     */
-    private R<StoreUserVo> checkPassword(StoreUser user, String password) {
-        return Objects.equals(password, user.getPassword())
-                ? storeUserService.createToKen(user)
-                : R.fail("密码错误");
+        return storeUserService.getById(accountId).getStatus() != 1 ? R.success("账号正常") : R.fail("账号禁用");
     }
 
     @ApiOperation("修改密码/忘记密码/更换绑定手机号")
     @ApiOperationSupport(order = 2)
-    @ApiImplicitParams({@ApiImplicitParam(name = "phone", value = "手机号", dataType = "String", paramType = "query", required = true),
-            @ApiImplicitParam(name = "newPhone", value = "新手机号", dataType = "String", paramType = "query"),
-            @ApiImplicitParam(name = "oldPassword", value = "旧密码", dataType = "String", paramType = "query"),
-            @ApiImplicitParam(name = "newPassword", value = "新密码", dataType = "String", paramType = "query"),
-            @ApiImplicitParam(name = "confirmNewPassword", value = "新密码确认", dataType = "String", paramType = "query"),
-            @ApiImplicitParam(name = "verificationCode", value = "验证码", dataType = "String", paramType = "query", required = true),
-            @ApiImplicitParam(name = "type", value = "类型:0:忘记密码,1:修改密码,2:更换绑定手机号", dataType = "Integer", paramType = "query", required = true)})
+    @ApiImplicitParams({@ApiImplicitParam(name = "phone", value = "手机号", dataType = "String", paramType = "query", required = true), @ApiImplicitParam(name = "newPhone", value = "新手机号", dataType = "String", paramType = "query"), @ApiImplicitParam(name = "oldPassword", value = "旧密码", dataType = "String", paramType = "query"), @ApiImplicitParam(name = "newPassword", value = "新密码", dataType = "String", paramType = "query"), @ApiImplicitParam(name = "confirmNewPassword", value = "新密码确认", dataType = "String", paramType = "query"), @ApiImplicitParam(name = "verificationCode", value = "验证码", dataType = "String", paramType = "query", required = true), @ApiImplicitParam(name = "type", value = "类型:0:忘记密码,1:修改密码,2:更换绑定手机号", dataType = "Integer", paramType = "query", required = true)})
     @GetMapping("/updatePassword")
-    public R<String> updatePassword(String phone,String newPhone, String oldPassword, String newPassword, String confirmNewPassword,String verificationCode, Integer type) {
-        log.info("StoreUserController.updatePassword?phone={}&newPhone&oldPassword={}&newPassword={}&confirmNewPassword={}&verificationCode={}&type={}", phone, newPhone, oldPassword, newPassword, confirmNewPassword, verificationCode, type);
-        return  storeUserService.forgetOrModifyPassword(phone, newPhone, oldPassword, newPassword, confirmNewPassword, verificationCode, type);
+    public R<String> updatePassword(String phone, String newPhone, String oldPassword, String newPassword, String confirmNewPassword, String verificationCode, Integer type) {
+        log.info("StoreUserController.updatePassword?phone={}&newPhone={}&oldPassword={}&newPassword={}&confirmNewPassword={}&verificationCode={}&type={}", phone, newPhone, oldPassword, newPassword, confirmNewPassword, verificationCode, type);
+        // 2025-11-04 验证码-商家端修改密码/忘记密码/更换绑定手机号
+        String businessType = "";
+        switch (type) {
+            case 0:
+                businessType = "forget_password";
+                break;
+            case 1:
+                businessType = "modify_password";
+                break;
+            case 2:
+                businessType = "modify_phone";
+                break;
+        }
+        String cacheCode = baseRedisService.getString("verification_store_" + businessType + "_" + phone);
+        if (null == cacheCode) {
+            return R.fail("当验证码过期或未发送");
+        }
+        if (!cacheCode.trim().equals(verificationCode.trim())) {
+            return R.fail("验证码错误");
+        }
+        return storeUserService.forgetOrModifyPassword(phone, newPhone, oldPassword, newPassword, confirmNewPassword, verificationCode, type);
     }
 
     @ApiOperation("更换绑定手机号效验原密码或验证码")
     @ApiOperationSupport(order = 2)
-    @ApiImplicitParams({@ApiImplicitParam(name = "phone", value = "手机号", dataType = "String", paramType = "query", required = true),
-            @ApiImplicitParam(name = "oldPassword", value = "旧密码", dataType = "String", paramType = "query"),
-            @ApiImplicitParam(name = "verificationCode", value = "验证码", dataType = "String", paramType = "query")})
+    @ApiImplicitParams({@ApiImplicitParam(name = "phone", value = "手机号", dataType = "String", paramType = "query", required = true), @ApiImplicitParam(name = "oldPassword", value = "旧密码", dataType = "String", paramType = "query"), @ApiImplicitParam(name = "verificationCode", value = "验证码", dataType = "String", paramType = "query")})
     @GetMapping("/changePhoneVerification")
     public R<Map<String, String>> changePhoneVerification(String phone, String oldPassword, String verificationCode) {
-        log.info("StoreUserController.changePhoneVerification?phone={}&oldPassword={}&verificationCode={}", phone,  oldPassword,  verificationCode);
+        log.info("StoreUserController.changePhoneVerification?phone={}&oldPassword={}&verificationCode={}", phone, oldPassword, verificationCode);
+        // 2025-11-04 验证码-商家端更换绑定手机号效验原密码或验证码
+        String cacheCode = baseRedisService.getString("verification_store_modify_phone_" + phone);
+        if (null == cacheCode) {
+            return R.fail("当验证码过期或未发送");
+        }
+        if (!cacheCode.trim().equals(verificationCode.trim())) {
+            return R.fail("验证码错误");
+        }
         try {
             return R.data(storeUserService.changePhoneVerification(phone, oldPassword, verificationCode));
         } catch (Exception e) {
@@ -160,10 +147,7 @@ public class StoreUserController {
 
     @ApiOperation("修改用户状态")
     @ApiOperationSupport(order = 6)
-    @ApiImplicitParams({
-            @ApiImplicitParam(name = "phone", value = "手机号", dataType = "String", paramType = "query", required = true),
-            @ApiImplicitParam(name = "type", value = "修改类型 (pass:密码状态改为1, face:人脸状态改为2)", dataType = "String", paramType = "query", required = true)
-    })
+    @ApiImplicitParams({@ApiImplicitParam(name = "phone", value = "手机号", dataType = "String", paramType = "query", required = true), @ApiImplicitParam(name = "type", value = "修改类型 (pass:密码状态改为1, face:人脸状态改为2)", dataType = "String", paramType = "query", required = true)})
     @GetMapping("/updateUserStatus")
     public R<StoreUser> updateUserStatus(String phone, String type) {
         log.info("StoreUserController.updateUserStatus?phone={}&type={}", phone, type);
@@ -180,9 +164,7 @@ public class StoreUserController {
         log.info("StoreUserController.setStoreUserInfo?storeInfo={}", storeInfo);
 
         // 查询店铺图片信息
-        LambdaQueryWrapper<StoreImg> queryWrapper = new LambdaQueryWrapper<StoreImg>()
-                .eq(StoreImg::getImgType, 10)
-                .eq(StoreImg::getStoreId, storeInfo.getId());
+        LambdaQueryWrapper<StoreImg> queryWrapper = new LambdaQueryWrapper<StoreImg>().eq(StoreImg::getImgType, 10).eq(StoreImg::getStoreId, storeInfo.getId());
         StoreImg storeImg = storeImgMapper.selectOne(queryWrapper);
 
         // 如果查询到店铺图片信息,则更新图片 URL
@@ -224,19 +206,8 @@ public class StoreUserController {
     @ApiOperation("web端查询用户列表")
     @ApiOperationSupport(order = 7)
     @GetMapping("/getStoreUserList")
-    @ApiImplicitParams({
-            @ApiImplicitParam(name = "pageNum", value = "页数", dataType = "int", paramType = "query", required = true),
-            @ApiImplicitParam(name = "pageSize", value = "页容", dataType = "int", paramType = "query", required = true),
-            @ApiImplicitParam(name = "storeName", value = "门店名称", dataType = "String", paramType = "query"),
-            @ApiImplicitParam(name = "storeContact", value = "门店联系人", dataType = "String", paramType = "query"),
-            @ApiImplicitParam(name = "storePhone", value = "门店电话", dataType = "String", paramType = "query"),
-            @ApiImplicitParam(name = "storeType", value = "门店类型", dataType = "String", paramType = "query")
-    })
-    public R<IPage<StoreUserVo>> getStoreUserList(@RequestParam(defaultValue = "1") int pageNum,
-                                                  @RequestParam(defaultValue = "10") int pageSize,
-                                                  @RequestParam(required = false) String id,
-                                                  @RequestParam(required = false) String phone,
-                                                  @RequestParam(required = false) Integer status) {
+    @ApiImplicitParams({@ApiImplicitParam(name = "pageNum", value = "页数", dataType = "int", paramType = "query", required = true), @ApiImplicitParam(name = "pageSize", value = "页容", dataType = "int", paramType = "query", required = true), @ApiImplicitParam(name = "storeName", value = "门店名称", dataType = "String", paramType = "query"), @ApiImplicitParam(name = "storeContact", value = "门店联系人", dataType = "String", paramType = "query"), @ApiImplicitParam(name = "storePhone", value = "门店电话", dataType = "String", paramType = "query"), @ApiImplicitParam(name = "storeType", value = "门店类型", dataType = "String", paramType = "query")})
+    public R<IPage<StoreUserVo>> getStoreUserList(@RequestParam(defaultValue = "1") int pageNum, @RequestParam(defaultValue = "10") int pageSize, @RequestParam(required = false) String id, @RequestParam(required = false) String phone, @RequestParam(required = false) Integer status) {
         log.info("StoreInfoController.getStoreUserList?pageNum={},pageSize={},id={},phone={},status={}", pageNum, pageSize, id, phone, status);
         R<IPage<StoreUserVo>> storeUserVoR = storeUserService.getStoreUserList(pageNum, pageSize, id, phone, status);
         return storeUserVoR;
@@ -304,15 +275,9 @@ public class StoreUserController {
     @ApiOperation(value = "web端导出商家端账号相关信息")
     @ApiOperationSupport(order = 6)
     @GetMapping("/exportExcel")
-    @ApiImplicitParams({
-            @ApiImplicitParam(name = "id", value = "用户id", dataType = "String", paramType = "query"),
-            @ApiImplicitParam(name = "phone", value = "联系电话", dataType = "String", paramType = "query"),
-            @ApiImplicitParam(name = "status", value = "状态", dataType = "String", paramType = "query"),
-    })
+    @ApiImplicitParams({@ApiImplicitParam(name = "id", value = "用户id", dataType = "String", paramType = "query"), @ApiImplicitParam(name = "phone", value = "联系电话", dataType = "String", paramType = "query"), @ApiImplicitParam(name = "status", value = "状态", dataType = "String", paramType = "query"),})
     @ResponseBody
-    public R exportExcel(@RequestParam(value = "id", required = false) String id
-            , @RequestParam(value = "phone", required = false) String phone
-            , @RequestParam(value = "status", required = false) String status) throws IOException {
+    public R exportExcel(@RequestParam(value = "id", required = false) String id, @RequestParam(value = "phone", required = false) String phone, @RequestParam(value = "status", required = false) String status) throws IOException {
         log.info("StoreInfoController.exportExcel");
         String excelPath = storeUserService.exportExcel(id, phone, status);
         return R.data(excelPath);
@@ -328,10 +293,18 @@ public class StoreUserController {
      */
     @ApiOperation("门店用户注册")
     @ApiOperationSupport(order = 7)
-    @ApiImplicitParams({@ApiImplicitParam(name = "phone", value = "手机号", dataType = "String", paramType = "query", required = true),
-            @ApiImplicitParam(name = "password", value = "密码", dataType = "String", paramType = "query", required = true)})
+    @ApiImplicitParams({@ApiImplicitParam(name = "phone", value = "手机号", dataType = "String", paramType = "query", required = true), @ApiImplicitParam(name = "password", value = "密码", dataType = "String", paramType = "query", required = true), @ApiImplicitParam(name = "code", value = "验证码", dataType = "String", paramType = "query", required = true)})
     @GetMapping("/register")
-    public R<Boolean> register(String phone, String password) {
+    public R<Boolean> register(@RequestParam("phone") String phone, @RequestParam("password") String password, @RequestParam("code") String code) {
+        log.info("StoreUserController.register?phone={}&password={}&code={}", phone, password, code);
+        // 2025-11-04 验证码-商家端注册
+        String cacheCode = baseRedisService.getString("verification_store_register_" + phone);
+        if (null == cacheCode) {
+            return R.fail("当验证码过期或未发送");
+        }
+        if (!cacheCode.trim().equals(code.trim())) {
+            return R.fail("验证码错误");
+        }
         log.info("StoreUserController.register?phone={}&password={}", phone, password);
         return storeUserService.register(phone, password);
     }
@@ -376,7 +349,7 @@ public class StoreUserController {
     @PostMapping("/storeCancelAccountVerification")
     public R<Map<String, String>> storeCancelAccountVerification(@RequestBody StoreUserVo storeUserVo) {
         log.info("StoreUserController.storeCancelAccountVerification?storeUserVo={}", storeUserVo);
-             return R.data(storeUserService.storeCancelAccountVerification(storeUserVo));
+        return R.data(storeUserService.storeCancelAccountVerification(storeUserVo));
     }
 
     /**
@@ -386,6 +359,14 @@ public class StoreUserController {
     @PostMapping("/storeCancelAccount")
     public R<StoreUserVo> storeCancelAccount(@RequestBody StoreUserVo storeUserVo) {
         log.info("StoreUserController.storeCancelAccount?storeUserVo={}", storeUserVo);
+        // 2025-11-04 验证码-商家端注册
+        String cacheCode = baseRedisService.getString("verification_store_cancel_account_" + storeUserVo.getPhone());
+        if (null == cacheCode) {
+            return R.fail("当验证码过期或未发送");
+        }
+        if (!cacheCode.trim().equals(storeUserVo.getVerificationCode().trim())) {
+            return R.fail("验证码错误");
+        }
         try {
             storeUserService.storeCancelAccount(storeUserVo);
         } catch (Exception e) {
@@ -421,9 +402,9 @@ public class StoreUserController {
      */
     @ApiOperation("是否有支付密码")
     @GetMapping("/havePayPassword")
-    public R<Boolean> havePayPassword(@RequestParam String storeUserId,@RequestParam(required = false) String password){
-        log.info("StoreUserController.havePayPassword?storeUserId={},password={}",storeUserId,password);
-        return storeUserService.havePayPassword(storeUserId,password);
+    public R<Boolean> havePayPassword(@RequestParam String storeUserId, @RequestParam(required = false) String password) {
+        log.info("StoreUserController.havePayPassword?storeUserId={},password={}", storeUserId, password);
+        return storeUserService.havePayPassword(storeUserId, password);
     }
 
     /**
@@ -434,8 +415,8 @@ public class StoreUserController {
     public R<Integer> addAlipayAccount(@RequestBody StoreUserVo storeUserVo) {
         log.info("StoreUserController.addAlipayAccount?storeUserVo={}", storeUserVo);
         LambdaUpdateWrapper<StoreUser> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
-        lambdaUpdateWrapper.set(StoreUser :: getAlipayAccount, storeUserVo.getAlipayAccount());
-        lambdaUpdateWrapper.eq(StoreUser :: getId, storeUserVo.getId());
-        return R.data(storeUserMapper.update(null,lambdaUpdateWrapper));
+        lambdaUpdateWrapper.set(StoreUser::getAlipayAccount, storeUserVo.getAlipayAccount());
+        lambdaUpdateWrapper.eq(StoreUser::getId, storeUserVo.getId());
+        return R.data(storeUserMapper.update(null, lambdaUpdateWrapper));
     }
 }

+ 93 - 127
alien-store/src/main/java/shop/alien/store/service/impl/StoreUserServiceImpl.java

@@ -104,7 +104,7 @@ public class StoreUserServiceImpl extends ServiceImpl<StoreUserMapper, StoreUser
         lambdaQueryWrapper.eq(StoreUser::getPhone, phone);
         StoreUser user = this.getOne(lambdaQueryWrapper);
         StoreUserVo storeUserVo = new StoreUserVo();
-        if(user != null){
+        if (user != null) {
             if (user.getStatus() == -1) {
                 LocalDateTime localDateTime = user.getLogoutTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
                 LocalDateTime future = localDateTime.plusDays(7);
@@ -113,7 +113,7 @@ public class StoreUserServiceImpl extends ServiceImpl<StoreUserMapper, StoreUser
                 long correct = duration.toMillis();
                 storeUserVo.setCountdown(correct);
             }
-        }else {
+        } else {
             return new StoreUserVo();
         }
         BeanUtils.copyProperties(user, storeUserVo);
@@ -210,7 +210,7 @@ public class StoreUserServiceImpl extends ServiceImpl<StoreUserMapper, StoreUser
         try {
             //类型为0 忘记密码
             if (type == 0) {
-                return forgetPassword(phone, newPassword, verificationCode);
+                return forgetPassword(phone, newPassword);
             }
             //修改密码
             else if (type == 1) {
@@ -236,7 +236,7 @@ public class StoreUserServiceImpl extends ServiceImpl<StoreUserMapper, StoreUser
             }
             //更换绑定手机号
             else if (type == 2) {
-                return ChangeBoundPhone(phone, newPhone, verificationCode);
+                return ChangeBoundPhone(phone, newPhone);
             }
             return R.success("密码修改成功");
         } catch (Exception e) {
@@ -247,7 +247,6 @@ public class StoreUserServiceImpl extends ServiceImpl<StoreUserMapper, StoreUser
     @Override
     public Map<String, String> changePhoneVerification(String phone, String oldPassword, String verificationCode) {
         Map<String, String> changePhoneMap = new HashMap<>();
-
         if (oldPassword != null && !oldPassword.equals("")) {
             LambdaUpdateWrapper<StoreUser> userLambdaUpdateWrapper = new LambdaUpdateWrapper<>();
             userLambdaUpdateWrapper.eq(StoreUser::getPhone, phone);
@@ -260,19 +259,8 @@ public class StoreUserServiceImpl extends ServiceImpl<StoreUserMapper, StoreUser
                 return changePhoneMap;
             }
         } else {
-            String oldPhoneVerification = "verification_" + phone;
-            //获取新手机号验证码
-            String redisVerificationCode = baseRedisService.getString(oldPhoneVerification);
-            if(StringUtils.isEmpty(redisVerificationCode)){
-                throw new RuntimeException("验证码已失效 请重新获取验证码");
-            }
-            if (redisVerificationCode.equals(verificationCode)) {
-                changePhoneMap.put("verificationStatus", "1");
-                return changePhoneMap;
-            } else {
-                changePhoneMap.put("verificationStatus", "0");
-                return changePhoneMap;
-            }
+            changePhoneMap.put("verificationStatus", "0");
+            return changePhoneMap;
         }
     }
 
@@ -297,62 +285,50 @@ public class StoreUserServiceImpl extends ServiceImpl<StoreUserMapper, StoreUser
         }
     }
 
-    private R<String> forgetPassword(String phone, String newPassword, String verificationCode) {
+    private R<String> forgetPassword(String phone, String newPassword) {
+        boolean flag = false;
+        LambdaUpdateWrapper<StoreUser> updateWrapper = new LambdaUpdateWrapper<>();
+        updateWrapper.eq(StoreUser::getPhone, phone);
+        updateWrapper.set(StoreUser::getPassword, newPassword);
+        flag = this.update(updateWrapper);
+        if (flag) {
+            log.info("密码修改成功");
+            String token = "store_" + phone;
+            String tokenStr = baseRedisService.getString(token);
+            if (tokenStr != null) {
+                baseRedisService.delete(token);
+            }
+        }
+        if (!flag) {
+            log.error("密码修改失败");
+            throw new RuntimeException("密码修改失败");
+        }
+        return R.success("密码修改成功");
+    }
+
+    private R<String> ChangeBoundPhone(String phone, String newPhone) {
         boolean flag = false;
-        String key = "verification_" + phone;
-        String redisVerificationCode = baseRedisService.getString(key);
-        if (!StringUtils.isEmpty(redisVerificationCode) && redisVerificationCode.equals(verificationCode)) {
+        //获取新手机号验证码
+        LambdaUpdateWrapper<StoreUser> newUpdateWrapper = new LambdaUpdateWrapper<>();
+        newUpdateWrapper.eq(StoreUser::getPhone, newPhone);
+        StoreUser newStoreUser = this.getOne(newUpdateWrapper);
+        if (newStoreUser != null) {
+            if (newStoreUser.getPhone().equals(newPhone)) {
+                throw new RuntimeException("该手机号已注册过商户");
+            }
+        } else {
             LambdaUpdateWrapper<StoreUser> updateWrapper = new LambdaUpdateWrapper<>();
             updateWrapper.eq(StoreUser::getPhone, phone);
-            updateWrapper.set(StoreUser::getPassword, newPassword);
-            flag = this.update(updateWrapper);
+            StoreUser storeUser = this.getOne(updateWrapper);
+            storeUser.setPhone(newPhone);
+            flag = this.updateById(storeUser);
             if (flag) {
-                log.info("密码修改成功");
                 String token = "store_" + phone;
                 String tokenStr = baseRedisService.getString(token);
                 if (tokenStr != null) {
                     baseRedisService.delete(token);
                 }
             }
-            if (!flag) {
-                log.error("密码修改失败");
-                throw new RuntimeException("密码修改失败");
-            }
-            return R.success("密码修改成功");
-        } else {
-            throw new RuntimeException("验证码错误");
-        }
-    }
-
-    private R<String> ChangeBoundPhone(String phone, String newPhone, String verificationCode) {
-        boolean flag = false;
-        String newPhoneVerification = "verification_" + newPhone;
-        //获取新手机号验证码
-        String redisVerificationCode = baseRedisService.getString(newPhoneVerification);
-        if (!StringUtils.isEmpty(redisVerificationCode) && redisVerificationCode.equals(verificationCode)) {
-            LambdaUpdateWrapper<StoreUser> newUpdateWrapper = new LambdaUpdateWrapper<>();
-            newUpdateWrapper.eq(StoreUser::getPhone, newPhone);
-            StoreUser newStoreUser = this.getOne(newUpdateWrapper);
-            if (newStoreUser != null) {
-                if (newStoreUser.getPhone().equals(newPhone)) {
-                    throw new RuntimeException("该手机号已注册过商户");
-                }
-            } else {
-                LambdaUpdateWrapper<StoreUser> updateWrapper = new LambdaUpdateWrapper<>();
-                updateWrapper.eq(StoreUser::getPhone, phone);
-                StoreUser storeUser = this.getOne(updateWrapper);
-                storeUser.setPhone(newPhone);
-                flag = this.updateById(storeUser);
-                if (flag) {
-                    String token = "store_" + phone;
-                    String tokenStr = baseRedisService.getString(token);
-                    if (tokenStr != null) {
-                        baseRedisService.delete(token);
-                    }
-                }
-            }
-        } else {
-            throw new RuntimeException("验证码错误");
         }
         return R.success("新手机号绑定成功");
     }
@@ -662,57 +638,47 @@ public class StoreUserServiceImpl extends ServiceImpl<StoreUserMapper, StoreUser
         LambdaQueryWrapper<StoreUser> storeUserLambdaQueryWrapper = new LambdaQueryWrapper<>();
         storeUserLambdaQueryWrapper.eq(StoreUser::getId, storeUserVo.getId());
         StoreUser storeUser = storeUserMapper.selectOne(storeUserLambdaQueryWrapper);
-        String key = "verification_" + storeUser.getPhone();
-        String redisVerificationCode = baseRedisService.getString(key);
-        if (redisVerificationCode != null) {
-            if (redisVerificationCode.equals(storeUserVo.getVerificationCode())) {
-                if (storeUser != null) {
-                    // 添加注销原因
-                    storeUser.setLogoutReason(storeUserVo.getLogoutReason());
-                    // 添加注销code
-                    storeUser.setLogoutCode(storeUserVo.getLogoutCode());
-                    // 注销中状态
-                    storeUser.setStatus(-1);
-                    // 添加注销申请时间
-                    storeUser.setLogoutTime(new Date());
-                    // 更新logout_flag状态为1
-                    storeUser.setLogoutFlag(1);
-                    int num = storeUserMapper.updateById(storeUser);
-                    if (num > 0) {
-                        // 发送通知
-                        LifeNotice lifeMessage = new LifeNotice();
-                        lifeMessage.setReceiverId("store_" + storeUser.getPhone());
-                        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
-                        String storeDate = simpleDateFormat.format(new Date());
-                        String text = "您在"+storeDate+"注销了账号,平台将为您保留7天,在此期间您可撤销注销账号,7天后将会永久删除。";
-                        com.alibaba.fastjson2.JSONObject jsonObject = new com.alibaba.fastjson2.JSONObject();
-                        jsonObject.put("message", text);
-                        lifeMessage.setContext(jsonObject.toJSONString());
-                        lifeMessage.setTitle("注销商家账号通知");
-                        lifeMessage.setSenderId("system");
-                        lifeMessage.setIsRead(0);
-                        lifeMessage.setNoticeType(1);
-                        lifeNoticeMapper.insert(lifeMessage);
-
-                        WebSocketVo websocketVo = new WebSocketVo();
-                        websocketVo.setSenderId("system");
-                        websocketVo.setReceiverId("store_" + storeUser.getPhone());
-                        websocketVo.setCategory("notice");
-                        websocketVo.setNoticeType("1");
-                        websocketVo.setIsRead(0);
-                        websocketVo.setText(com.alibaba.fastjson2.JSONObject.from(lifeMessage).toJSONString());
-                        try {
-                            webSocketProcess.sendMessage("store_" + storeUser.getPhone(), com.alibaba.fastjson2.JSONObject.from(websocketVo).toJSONString());
-                        } catch (Exception e) {
-                            log.error("LifeUserViolationServiceImpl_approve Error Stack={}", e.getMessage());
-                        }
-                    }
+        if (storeUser != null) {
+            // 添加注销原因
+            storeUser.setLogoutReason(storeUserVo.getLogoutReason());
+            // 添加注销code
+            storeUser.setLogoutCode(storeUserVo.getLogoutCode());
+            // 注销中状态
+            storeUser.setStatus(-1);
+            // 添加注销申请时间
+            storeUser.setLogoutTime(new Date());
+            // 更新logout_flag状态为1
+            storeUser.setLogoutFlag(1);
+            int num = storeUserMapper.updateById(storeUser);
+            if (num > 0) {
+                // 发送通知
+                LifeNotice lifeMessage = new LifeNotice();
+                lifeMessage.setReceiverId("store_" + storeUser.getPhone());
+                SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+                String storeDate = simpleDateFormat.format(new Date());
+                String text = "您在" + storeDate + "注销了账号,平台将为您保留7天,在此期间您可撤销注销账号,7天后将会永久删除。";
+                com.alibaba.fastjson2.JSONObject jsonObject = new com.alibaba.fastjson2.JSONObject();
+                jsonObject.put("message", text);
+                lifeMessage.setContext(jsonObject.toJSONString());
+                lifeMessage.setTitle("注销商家账号通知");
+                lifeMessage.setSenderId("system");
+                lifeMessage.setIsRead(0);
+                lifeMessage.setNoticeType(1);
+                lifeNoticeMapper.insert(lifeMessage);
+
+                WebSocketVo websocketVo = new WebSocketVo();
+                websocketVo.setSenderId("system");
+                websocketVo.setReceiverId("store_" + storeUser.getPhone());
+                websocketVo.setCategory("notice");
+                websocketVo.setNoticeType("1");
+                websocketVo.setIsRead(0);
+                websocketVo.setText(com.alibaba.fastjson2.JSONObject.from(lifeMessage).toJSONString());
+                try {
+                    webSocketProcess.sendMessage("store_" + storeUser.getPhone(), com.alibaba.fastjson2.JSONObject.from(websocketVo).toJSONString());
+                } catch (Exception e) {
+                    log.error("LifeUserViolationServiceImpl_approve Error Stack={}", e.getMessage());
                 }
-            } else {
-                throw new RuntimeException("验证码错误");
             }
-        } else {
-            throw new RuntimeException("验证码错误");
         }
     }
 
@@ -741,7 +707,7 @@ public class StoreUserServiceImpl extends ServiceImpl<StoreUserMapper, StoreUser
             lifeMessage.setReceiverId("store_" + storeUser.getPhone());
             SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
             String storeDate = simpleDateFormat.format(storeUser.getCreatedTime());
-            String text = "您在"+storeDate+"撤销了注销账号,所有数据均已保留,您可继续在平台使用。";
+            String text = "您在" + storeDate + "撤销了注销账号,所有数据均已保留,您可继续在平台使用。";
             com.alibaba.fastjson2.JSONObject jsonObject = new com.alibaba.fastjson2.JSONObject();
             jsonObject.put("message", text);
             lifeMessage.setContext(jsonObject.toJSONString());
@@ -804,28 +770,28 @@ public class StoreUserServiceImpl extends ServiceImpl<StoreUserMapper, StoreUser
     }
 
     @Override
-    public R havePayPassword(String storeUserId,String password) {
+    public R havePayPassword(String storeUserId, String password) {
         StoreUser storeUser = storeUserMapper.selectById(storeUserId);
         HashMap<Object, Object> returnMap = new HashMap<>();
-        returnMap.put("code",200);
+        returnMap.put("code", 200);
         if (null == storeUser) {
-            returnMap.put("message","未查询到用户");
-            returnMap.put("data","false");
+            returnMap.put("message", "未查询到用户");
+            returnMap.put("data", "false");
             return R.data(returnMap);
         }
-        if( null == storeUser.getPayPassword()){
-            returnMap.put("message","用户未设置支付密码");
-            returnMap.put("data","false");
+        if (null == storeUser.getPayPassword()) {
+            returnMap.put("message", "用户未设置支付密码");
+            returnMap.put("data", "false");
             return R.data(returnMap);
-        } else if(null != password){
-            if( !password.equals(storeUser.getPayPassword())){
-                returnMap.put("message","密码错误");
-                returnMap.put("data","false");
+        } else if (null != password) {
+            if (!password.equals(storeUser.getPayPassword())) {
+                returnMap.put("message", "密码错误");
+                returnMap.put("data", "false");
                 return R.data(returnMap);
             }
         }
-        returnMap.put("data","true");
-        returnMap.put("message","用户已设置支付密码");
+        returnMap.put("data", "true");
+        returnMap.put("message", "用户已设置支付密码");
         return R.data(returnMap);
     }
 

+ 44 - 5
alien-store/src/main/java/shop/alien/store/util/ali/AliSms.java

@@ -43,21 +43,60 @@ public class AliSms {
     @Value("${ali.sms.templateCode}")
     private String templateCode;
 
+    @Value("${ali.sms.codeTimeOut}")
+    private Long codeTimeOut;
+
     /**
      * 发送验证码
      *
-     * @param phone 手机号
+     * @param phone        手机号
+     * @param appType      端区分(0:用户, 1:商家)
+     * @param businessType 业务类型 (0:登录, 1:修改密码, 2:注册, 3:修改手机号, 4:注销店铺, 5:注销账号, 6:忘记密码)
      * @return 验证码
      */
-    public Integer sendSms(String phone) {
-        log.info("AliSmsConfig.sendSms?phone={}", phone);
+    public Integer sendSms(String phone, Integer appType, Integer businessType) {
+        log.info("AliSmsConfig.sendSms?phone={}&appType={}&businessType={}", phone, appType, businessType);
         try {
+            String appTypeStr = appType == 0 ? "user" : "store";
+            String businessTypeStr;
+            switch (businessType) {
+                case 0:
+                    //登录
+                    businessTypeStr = "login";
+                    break;
+                case 1:
+                    //修改密码
+                    businessTypeStr = "modify_password";
+                    break;
+                case 2:
+                    //注册
+                    businessTypeStr = "register";
+                    break;
+                case 3:
+                    //修改手机号
+                    businessTypeStr = "modify_phone";
+                    break;
+                case 4:
+                    //注销店铺
+                    businessTypeStr = "cancel_store";
+                    break;
+                case 5:
+                    //注销账号
+                    businessTypeStr = "cancel_account";
+                    break;
+                case 6:
+                    //忘记密码
+                    businessTypeStr = "forget_password";
+                    break;
+                default:
+                    businessTypeStr = "login";
+            }
             // -----------------测试用手机号--------------------------------------------------------------------------------------------
             List<String> phoneList = Arrays.asList("19999990001", "19999990002", "19999990003", "19999990004", "19999990005", "19999990006", "19999990007", "19999990008", "19999990009", "19999990010",
                     "16666660001", "16666660002", "16666660003", "16666660004", "16666660005", "16666660006", "16666660007", "16666660008", "16666660009", "16666660010");
             if (phoneList.contains(phone)) {
                 // 验证码发送成功,将验证码保存到redis中 设置60秒过期
-                baseRedisService.setString("verification_"+phone,"123456",Long.valueOf(60));
+                baseRedisService.setString("verification_" + appTypeStr + "_" + businessTypeStr + "_" + phone, "123456", codeTimeOut);
                 return 123456;
             }
             // -----------------测试用手机号--------------------------------------------------------------------------------------------
@@ -87,7 +126,7 @@ public class AliSms {
                 return null;
             }
             // 验证码发送成功,将验证码保存到redis中 设置60秒过期
-            baseRedisService.setString("verification_"+phone,code.toString(),Long.valueOf(60));
+            baseRedisService.setString("verification_" + appTypeStr + "_" + businessTypeStr + "_" + phone, code.toString(), codeTimeOut);
             return code;
         } catch (Exception e) {
             log.error("AliSmsConfig.sendSms ERROR Msg={}", e.getMessage());

+ 7 - 0
pom.xml

@@ -359,6 +359,13 @@
                 <version>3.4.0</version>
             </dependency>
 
+            <!-- 验证码 -->
+            <dependency>
+                <groupId>pro.fessional</groupId>
+                <artifactId>kaptcha</artifactId>
+                <version>2.3.3</version>
+            </dependency>
+
             <!--Other End-->
         </dependencies>
     </dependencyManagement>