Sfoglia il codice sorgente

fix: 注册验证码功能

liaoyitao 1 mese fa
parent
commit
439f8e5a21

+ 5 - 0
pom.xml

@@ -34,6 +34,11 @@
34 34
     </repositories>
35 35
     <dependencies>
36 36
         <dependency>
37
+            <groupId>com.aliyun</groupId>
38
+            <artifactId>dysmsapi20170525</artifactId>
39
+            <version>3.0.0</version>
40
+        </dependency>
41
+        <dependency>
37 42
             <groupId>org.springframework.boot</groupId>
38 43
             <artifactId>spring-boot-starter-data-jpa</artifactId>
39 44
         </dependency>

+ 1 - 0
src/main/java/com/lqkj/link/config/WebSecurityConfig.java

@@ -45,6 +45,7 @@ public class WebSecurityConfig {
45 45
                                 "/swagger-ui/**",
46 46
                                 "/v3/api-docs/**",
47 47
                                 "/geom/all",
48
+                                "/user/sendSms",
48 49
                                 "/upload/**")
49 50
                         .permitAll()
50 51
                         .requestMatchers("/**")

+ 19 - 3
src/main/java/com/lqkj/link/module/authority/controller/UserInfoController.java

@@ -2,6 +2,7 @@ package com.lqkj.link.module.authority.controller;
2 2
 
3 3
 import com.alibaba.fastjson2.JSON;
4 4
 import com.alibaba.fastjson2.JSONObject;
5
+import com.lqkj.link.config.LimitRequest.LimitRequest;
5 6
 import com.lqkj.link.message.MessageBean;
6 7
 import com.lqkj.link.module.authority.domain.UserInfo;
7 8
 import com.lqkj.link.module.authority.service.UserInfoService;
@@ -29,6 +30,7 @@ import static com.lqkj.link.APIVersion.VERSION_V1;
29 30
 @RestController
30 31
 @RequestMapping("/user")
31 32
 @Tag(name = "用户管理", description = "用户管理")
33
+@LimitRequest
32 34
 public class UserInfoController {
33 35
     private final UserInfoService userInfoService;
34 36
     private final JwtService jwtService;
@@ -208,14 +210,16 @@ public class UserInfoController {
208 210
             )
209 211
     )
210 212
     @PostMapping("/manage/register")
211
-    public MessageBean register(@RequestBody UserInfo userInfo, HttpServletRequest request) {
213
+    public MessageBean register(@RequestBody UserInfo userInfo,
214
+                                @RequestParam(name = "captcha") String captcha,
215
+                                HttpServletRequest request) {
212 216
         String authHeader = request.getHeader("Authorization");
213 217
         String userCode = null;
214 218
         if (Objects.nonNull(authHeader)){
215 219
              userCode = jwtService.decryptUsernameWithHeader(authHeader);
216 220
         }
217
-        userInfoService.register(userInfo, false, userCode);
218
-        return MessageBean.ok(null, "保存用户接口");
221
+        userInfoService.register(userInfo, false, userCode, captcha);
222
+        return MessageBean.ok(null, "注册用户接口");
219 223
     }
220 224
 
221 225
 
@@ -235,4 +239,16 @@ public class UserInfoController {
235 239
         userInfoService.saveSettings(autosaveTime, movingSpeed, userCode);
236 240
         return MessageBean.ok(null, "保存用户设置");
237 241
     }
242
+
243
+    /**
244
+     * 发送短信验证码
245
+     * @param phoneNumber
246
+     * @return
247
+     */
248
+    @LimitRequest(time = 60 * 1000)
249
+    @PostMapping("/sendSms")
250
+    public MessageBean<String> sendSms(@RequestParam String phoneNumber) {
251
+        userInfoService.sendSms(phoneNumber);
252
+        return MessageBean.ok(null, "发送短信成功");
253
+    }
238 254
 }

+ 39 - 0
src/main/java/com/lqkj/link/module/authority/domain/Captcha.java

@@ -0,0 +1,39 @@
1
+package com.lqkj.link.module.authority.domain;
2
+
3
+import com.fasterxml.jackson.annotation.JsonFormat;
4
+import io.swagger.v3.oas.annotations.media.Schema;
5
+import jakarta.persistence.*;
6
+import lombok.AllArgsConstructor;
7
+import lombok.Getter;
8
+import lombok.NoArgsConstructor;
9
+import lombok.Setter;
10
+
11
+import java.util.Date;
12
+import java.util.List;
13
+
14
+@Entity
15
+@Table(name = "captcha")
16
+@Getter
17
+@Setter
18
+@NoArgsConstructor
19
+@AllArgsConstructor
20
+public class Captcha {
21
+    @Id
22
+    @Column(name = "id")
23
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
24
+    @Schema(description = "验证码id")
25
+    private Integer id;
26
+
27
+    @Column(name = "code")
28
+    @Schema(description = "验证码")
29
+    private String code;
30
+
31
+    @Column(name = "phone_number")
32
+    @Schema(description = "手机号")
33
+    private String phoneNumber;
34
+
35
+    @Column(name = "create_time")
36
+    @JsonFormat(pattern = "YYYY-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
37
+    private Date createTime;
38
+
39
+}

+ 23 - 0
src/main/java/com/lqkj/link/module/authority/repository/CaptchaRepository.java

@@ -0,0 +1,23 @@
1
+package com.lqkj.link.module.authority.repository;
2
+
3
+import com.lqkj.link.module.authority.domain.Captcha;
4
+import com.lqkj.link.module.authority.domain.UserInfo;
5
+import org.springframework.data.domain.Page;
6
+import org.springframework.data.domain.Pageable;
7
+import org.springframework.data.jpa.repository.JpaRepository;
8
+import org.springframework.data.jpa.repository.Modifying;
9
+import org.springframework.data.jpa.repository.Query;
10
+import org.springframework.data.repository.query.Param;
11
+import org.springframework.stereotype.Repository;
12
+import org.springframework.transaction.annotation.Transactional;
13
+
14
+import java.util.List;
15
+import java.util.Map;
16
+
17
+@Repository
18
+public interface CaptchaRepository extends JpaRepository<Captcha, Integer> {
19
+
20
+
21
+    @Query("select c from Captcha c where c.phoneNumber = :phoneNumber")
22
+    Captcha findByPhoneNumber(@Param("phoneNumber") String phoneNumber);
23
+}

+ 48 - 4
src/main/java/com/lqkj/link/module/authority/service/UserInfoService.java

@@ -1,12 +1,17 @@
1 1
 package com.lqkj.link.module.authority.service;
2 2
 
3 3
 import cn.hutool.core.util.RandomUtil;
4
+import com.lqkj.link.module.authority.domain.Captcha;
4 5
 import com.lqkj.link.module.authority.domain.UserInfo;
6
+import com.lqkj.link.module.authority.repository.CaptchaRepository;
5 7
 import com.lqkj.link.module.authority.repository.RoleInfoRepository;
6 8
 import com.lqkj.link.module.authority.repository.UserInfoRepository;
7 9
 import com.lqkj.link.util.RSAUtils;
10
+import com.lqkj.link.util.SendSmsUtils;
8 11
 import jakarta.annotation.PostConstruct;
9 12
 import org.bouncycastle.jcajce.PKIXCertRevocationCheckerParameters;
13
+import org.springframework.beans.factory.annotation.Autowired;
14
+import org.springframework.security.authentication.LockedException;
10 15
 import org.springframework.transaction.annotation.Transactional;
11 16
 import org.apache.commons.lang3.StringUtils;
12 17
 import org.springframework.data.domain.Page;
@@ -25,6 +30,9 @@ public class UserInfoService {
25 30
     private final PasswordEncoder passwordEncoder;
26 31
     private final RoleInfoRepository roleInfoRepository;
27 32
 
33
+    @Autowired
34
+    private CaptchaRepository captchaRepository;
35
+
28 36
     public UserInfoService(UserInfoRepository userInfoRepository, PasswordEncoder passwordEncoder, RoleInfoRepository roleInfoRepository) {
29 37
         this.userInfoRepository = userInfoRepository;
30 38
         this.passwordEncoder = passwordEncoder;
@@ -49,7 +57,7 @@ public class UserInfoService {
49 57
         guestUser.setLocking(false);
50 58
         guestUser.setRefreshResource(false);
51 59
         guestUser.setUpdateTime(new Date());
52
-        register(guestUser, true, null);
60
+        register(guestUser, true, null, null);
53 61
         return guestUser.getUserCode();
54 62
     }
55 63
 
@@ -207,13 +215,13 @@ public class UserInfoService {
207 215
      * @param userInfo
208 216
      */
209 217
     @Transactional
210
-    public void register(UserInfo userInfo, boolean isGuest, String userCode) {
218
+    public void register(UserInfo userInfo, boolean isGuest, String userCode, String captcha) {
211 219
         if (!isGuest){
212 220
             userInfo.setUserCode(RSAUtils.decryptBase64(userInfo.getUserCode()));
213 221
             if (StringUtils.isNotBlank(userInfo.getPassword()) && userInfo.getPassword().length() == 172) {
214 222
                 userInfo.setPassword(RSAUtils.decryptBase64(userInfo.getPassword()));
215 223
             }
216
-            checkerParameters(userInfo.getUserCode(), userInfo.getPassword());
224
+            checkerParameters(userInfo.getUserCode(), userInfo.getPassword(), captcha);
217 225
             assemblyParameter(userInfo, userCode);
218 226
         }
219 227
         userInfoRepository.save(userInfo);
@@ -247,25 +255,43 @@ public class UserInfoService {
247 255
      * @param userCode 用户账号
248 256
      * @param password 用户密码
249 257
      */
250
-    private void checkerParameters(String userCode, String password) {
258
+    private void checkerParameters(String userCode, String password, String captcha) {
251 259
         if (StringUtils.isBlank(userCode)) {
252 260
             throw new RuntimeException("账号不能为空!");
253 261
         }
254 262
         if (StringUtils.isBlank(password)) {
255 263
             throw new RuntimeException("密码不能为空!");
256 264
         }
265
+        if (StringUtils.isBlank(captcha)) {
266
+            throw new RuntimeException("验证码不能为空!");
267
+        }
257 268
         if (userInfoRepository.hasSameUserCode(userCode)) {
258 269
             throw new RuntimeException("该手机号已被注册!");
259 270
         }
260 271
         if (!userCode.matches("^(13[0-9]|14[5|7]|15[0|1|2|3|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\\d{8}$")){
261 272
             throw new RuntimeException("请输入正确的手机号!");
262 273
         }
274
+        checkCaptcha(userCode, captcha);
263 275
         if (!password.matches("^(?=.*\\d)(?=.*[a-z])(?=.*[A-Z])[a-zA-Z0-9]{8,16}$")){
264 276
             throw new RuntimeException("密码必须是8-16位数字、大写字母、小写字母!");
265 277
         }
266 278
 
267 279
     }
268 280
 
281
+    private void checkCaptcha(String userCode, String captcha) {
282
+        Captcha byPhoneNumber = captchaRepository.findByPhoneNumber(userCode);
283
+        if (Objects.isNull(byPhoneNumber)){
284
+            throw new RuntimeException("验证码错误!");
285
+        }
286
+        if ((new Date()).getTime() - byPhoneNumber.getCreateTime().getTime() > 1000 * 60 * 5){
287
+            throw new RuntimeException("验证码已过期,请重新获取");
288
+        }
289
+        if (!byPhoneNumber.getCode().equals(captcha)){
290
+            throw new RuntimeException("验证码错误!");
291
+        }
292
+
293
+    }
294
+
269 295
 
270 296
     /**
271 297
      * 保存ue设置
@@ -276,4 +302,22 @@ public class UserInfoService {
276 302
     public void saveSettings(Integer autosaveTime, Integer movingSpeed, String userCode) {
277 303
         userInfoRepository.saveSettings(autosaveTime, movingSpeed, userCode);
278 304
     }
305
+
306
+    @Transactional
307
+    public void sendSms(String phoneNumber) {
308
+        String code = SendSmsUtils.generateSMSCode();
309
+        Captcha captcha =  captchaRepository.findByPhoneNumber(phoneNumber);
310
+        if (Objects.nonNull(captcha)){
311
+            captcha.setCode(code);
312
+            captcha.setCreateTime(new Date());
313
+        }else {
314
+            captcha = new Captcha(null, code, phoneNumber, new Date());
315
+        }
316
+        captchaRepository.save(captcha);
317
+        try {
318
+            SendSmsUtils.sendSms(phoneNumber, code);
319
+        } catch (Exception e) {
320
+            throw new RuntimeException(e);
321
+        }
322
+    }
279 323
 }

+ 61 - 0
src/main/java/com/lqkj/link/util/SendSmsUtils.java

@@ -0,0 +1,61 @@
1
+package com.lqkj.link.util;
2
+
3
+import com.aliyun.tea.TeaException;
4
+
5
+import java.security.SecureRandom;
6
+
7
+public class SendSmsUtils {
8
+
9
+    private static final String ACCESSKEYID = "LTAI5tH3cQMGWBkPf6GLKEJe";
10
+
11
+    private static final String ACCESSKEYSECRET = "X1ErvZq31BBbEp8Pwe2weIhjUn4G6u";
12
+
13
+    public static String generateSMSCode() {
14
+        SecureRandom random = new SecureRandom();
15
+        // 生成一个100000到999999之间的随机整数
16
+        int randomNum = random.nextInt(900000) + 100000;
17
+        return String.valueOf(randomNum);
18
+    }
19
+
20
+    public static com.aliyun.dysmsapi20170525.Client createClient() throws Exception {
21
+        // 工程代码泄露可能会导致 AccessKey 泄露,并威胁账号下所有资源的安全性。以下代码示例仅供参考。
22
+        // 建议使用更安全的 STS 方式,更多鉴权访问方式请参见:https://help.aliyun.com/document_detail/378657.html。
23
+        com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config()
24
+                // 必填,请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_ID。
25
+                .setAccessKeyId(ACCESSKEYID)
26
+                // 必填,请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_SECRET。
27
+                .setAccessKeySecret(ACCESSKEYSECRET);
28
+        // Endpoint 请参考 https://api.aliyun.com/product/Dysmsapi
29
+        config.endpoint = "dysmsapi.aliyuncs.com";
30
+        return new com.aliyun.dysmsapi20170525.Client(config);
31
+    }
32
+
33
+    public static void sendSms(String phoneNumbers, String code) throws Exception {
34
+        com.aliyun.dysmsapi20170525.Client client = SendSmsUtils.createClient();
35
+        com.aliyun.dysmsapi20170525.models.SendSmsRequest sendSmsRequest = new com.aliyun.dysmsapi20170525.models.SendSmsRequest()
36
+                .setPhoneNumbers(phoneNumbers)
37
+                .setSignName("灵奇软件")
38
+                .setTemplateCode("SMS_470990023")
39
+                .setTemplateParam("{\"code\":\"" + code + "\"}");
40
+        com.aliyun.teautil.models.RuntimeOptions runtime = new com.aliyun.teautil.models.RuntimeOptions();
41
+        try {
42
+            // 复制代码运行请自行打印 API 的返回值
43
+            client.sendSmsWithOptions(sendSmsRequest, runtime);
44
+        } catch (TeaException error) {
45
+            // 此处仅做打印展示,请谨慎对待异常处理,在工程项目中切勿直接忽略异常。
46
+            // 错误 message
47
+            System.out.println(error.getMessage());
48
+            // 诊断地址
49
+            System.out.println(error.getData().get("Recommend"));
50
+            com.aliyun.teautil.Common.assertAsString(error.message);
51
+        } catch (Exception _error) {
52
+            TeaException error = new TeaException(_error.getMessage(), _error);
53
+            // 此处仅做打印展示,请谨慎对待异常处理,在工程项目中切勿直接忽略异常。
54
+            // 错误 message
55
+            System.out.println(error.getMessage());
56
+            // 诊断地址
57
+            System.out.println(error.getData().get("Recommend"));
58
+            com.aliyun.teautil.Common.assertAsString(error.message);
59
+        }
60
+    }
61
+}

+ 13 - 0
src/main/resources/db/migration/V8__2.0.5.sql

@@ -0,0 +1,13 @@
1
+
2
+create table captcha(
3
+    id               SERIAL                  not null,
4
+    code             varchar(10)             null,
5
+    phone_number     varchar(20)             null,
6
+    create_time       timestamp               null,
7
+    constraint pk_captcha primary key (id)
8
+);
9
+comment on table captcha is '验证码';
10
+comment on column captcha.id is 'id';
11
+comment on column captcha.code is '验证码';
12
+comment on column captcha.phone_number is '手机号';
13
+comment on column captcha.create_time is '创建时间';