diff --git a/enlish-service/pom.xml b/enlish-service/pom.xml index e8573f9..de04116 100644 --- a/enlish-service/pom.xml +++ b/enlish-service/pom.xml @@ -105,6 +105,55 @@ tess4j + + + com.aliyun + dysmsapi20170525 + + + + + cn.dev33 + sa-token-spring-boot3-starter + + + + cn.dev33 + sa-token-redis-template + + + + + org.springframework.security + spring-security-crypto + + + + org.apache.tomcat.embed + tomcat-embed-core + + + + org.springframework.boot + spring-boot-configuration-processor + + + + commons-codec + commons-codec + + + + io.jsonwebtoken + jjwt + + + + com.auth0 + java-jwt + + + diff --git a/enlish-service/src/main/java/com/yinlihupo/enlish/service/config/AliyunAccessKeyProperties.java b/enlish-service/src/main/java/com/yinlihupo/enlish/service/config/AliyunAccessKeyProperties.java new file mode 100644 index 0000000..4afb498 --- /dev/null +++ b/enlish-service/src/main/java/com/yinlihupo/enlish/service/config/AliyunAccessKeyProperties.java @@ -0,0 +1,13 @@ +package com.yinlihupo.enlish.service.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@ConfigurationProperties(prefix = "aliyun") +@Component +@Data +public class AliyunAccessKeyProperties { + private String accessKeyId; + private String accessKeySecret; +} diff --git a/enlish-service/src/main/java/com/yinlihupo/enlish/service/config/AliyunSmsClientConfig.java b/enlish-service/src/main/java/com/yinlihupo/enlish/service/config/AliyunSmsClientConfig.java new file mode 100644 index 0000000..1165857 --- /dev/null +++ b/enlish-service/src/main/java/com/yinlihupo/enlish/service/config/AliyunSmsClientConfig.java @@ -0,0 +1,36 @@ +package com.yinlihupo.enlish.service.config; + +import com.aliyun.dysmsapi20170525.Client; +import com.aliyun.teaopenapi.models.Config; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + + +@Configuration +@Slf4j +public class AliyunSmsClientConfig { + + @Resource + private AliyunAccessKeyProperties aliyunAccessKeyProperties; + + @Bean + public Client smsClient() { + try { + Config config = new Config() + // 必填 + .setAccessKeyId(aliyunAccessKeyProperties.getAccessKeyId()) + // 必填 + .setAccessKeySecret(aliyunAccessKeyProperties.getAccessKeySecret()); + + // Endpoint 请参考 https://api.aliyun.com/product/Dysmsapi + config.endpoint = "dysmsapi.aliyuncs.com"; + + return new Client(config); + } catch (Exception e) { + log.error("初始化阿里云短信发送客户端错误: ", e); + return null; + } + } +} diff --git a/enlish-service/src/main/java/com/yinlihupo/enlish/service/config/PasswordEncoderConfig.java b/enlish-service/src/main/java/com/yinlihupo/enlish/service/config/PasswordEncoderConfig.java new file mode 100644 index 0000000..db759a9 --- /dev/null +++ b/enlish-service/src/main/java/com/yinlihupo/enlish/service/config/PasswordEncoderConfig.java @@ -0,0 +1,19 @@ +package com.yinlihupo.enlish.service.config; + + +import org.springframework.context.annotation.Bean; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Component; + + +@Component +public class PasswordEncoderConfig { + + @Bean + public PasswordEncoder passwordEncoder() { + // BCrypt 是一种安全且适合密码存储的哈希算法,它在进行哈希时会自动加入“盐”,增加密码的安全性。 + return new BCryptPasswordEncoder(); + } + +} diff --git a/enlish-service/src/main/java/com/yinlihupo/enlish/service/constant/UserRedisConstants.java b/enlish-service/src/main/java/com/yinlihupo/enlish/service/constant/UserRedisConstants.java new file mode 100644 index 0000000..330f89e --- /dev/null +++ b/enlish-service/src/main/java/com/yinlihupo/enlish/service/constant/UserRedisConstants.java @@ -0,0 +1,10 @@ +package com.yinlihupo.enlish.service.constant; + +public class UserRedisConstants { + + public static final String USER_LOGIN_CODE = "user:login:code:"; + + public static String buildUserLoginCode(String phone) { + return USER_LOGIN_CODE + phone; + } +} diff --git a/enlish-service/src/main/java/com/yinlihupo/enlish/service/controller/LoginController.java b/enlish-service/src/main/java/com/yinlihupo/enlish/service/controller/LoginController.java new file mode 100644 index 0000000..df6190d --- /dev/null +++ b/enlish-service/src/main/java/com/yinlihupo/enlish/service/controller/LoginController.java @@ -0,0 +1,48 @@ +package com.yinlihupo.enlish.service.controller; + +import cn.dev33.satoken.stp.StpUtil; +import com.yinlihupo.enlish.service.model.vo.login.LoginReqVO; +import com.yinlihupo.enlish.service.model.vo.login.VerificationCodeReqVO; +import com.yinlihupo.enlish.service.service.LoginService; +import com.yinlihupo.framework.biz.operationlog.aspect.ApiOperationLog; +import com.yinlihupo.framework.common.response.Response; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +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; + +@RequestMapping("/login/") +@RestController +@Slf4j +public class LoginController { + + @Resource + private LoginService loginService; + + @PostMapping("login") + @ApiOperationLog(description = "登录") + public Response login(@RequestBody LoginReqVO loginReqVO) { + try { + loginService.login(loginReqVO.getPhone(), loginReqVO.getName(), loginReqVO.getPassword(), loginReqVO.getCode()); + return Response.success(StpUtil.getTokenInfo().getTokenValue()); + } catch (Exception e) { + log.error("注册或登录失败 {}", e.getMessage()); + return Response.fail("注册或登录失败 " + e.getMessage()); + } + } + + @PostMapping("sendVerificationCode") + @ApiOperationLog(description = "发送验证码") + public Response sendVerificationCode(@RequestBody VerificationCodeReqVO verificationCodeReqVO) { + try { + loginService.sendVerificationCode(verificationCodeReqVO.getPhone()); + return Response.success(); + } catch (Exception e) { + log.error("发送验证码失败 {}", e.getMessage()); + return Response.fail("发送验证码失败 " + e.getMessage()); + } + } + +} diff --git a/enlish-service/src/main/java/com/yinlihupo/enlish/service/domain/dataobject/UserDO.java b/enlish-service/src/main/java/com/yinlihupo/enlish/service/domain/dataobject/UserDO.java new file mode 100644 index 0000000..0c8ae31 --- /dev/null +++ b/enlish-service/src/main/java/com/yinlihupo/enlish/service/domain/dataobject/UserDO.java @@ -0,0 +1,29 @@ +package com.yinlihupo.enlish.service.domain.dataobject; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@NoArgsConstructor +@Data +@Builder +public class UserDO { + private Long id; + + private String password; + + private String name; + + private String openid; + + private String phone; + + private String email; + + private Integer status; + + private Integer isDeleted; + +} \ No newline at end of file diff --git a/enlish-service/src/main/java/com/yinlihupo/enlish/service/domain/mapper/UserDOMapper.java b/enlish-service/src/main/java/com/yinlihupo/enlish/service/domain/mapper/UserDOMapper.java new file mode 100644 index 0000000..4241985 --- /dev/null +++ b/enlish-service/src/main/java/com/yinlihupo/enlish/service/domain/mapper/UserDOMapper.java @@ -0,0 +1,10 @@ +package com.yinlihupo.enlish.service.domain.mapper; + +import com.yinlihupo.enlish.service.domain.dataobject.UserDO; + +public interface UserDOMapper { + + UserDO selectByPhone(String phone); + + void insert(UserDO userDO); +} \ No newline at end of file diff --git a/enlish-service/src/main/java/com/yinlihupo/enlish/service/model/vo/login/LoginReqVO.java b/enlish-service/src/main/java/com/yinlihupo/enlish/service/model/vo/login/LoginReqVO.java new file mode 100644 index 0000000..6508564 --- /dev/null +++ b/enlish-service/src/main/java/com/yinlihupo/enlish/service/model/vo/login/LoginReqVO.java @@ -0,0 +1,18 @@ +package com.yinlihupo.enlish.service.model.vo.login; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@NoArgsConstructor +@Data +@Builder +public class LoginReqVO { + + private String phone; + private String name; + private String password; + private String code; +} diff --git a/enlish-service/src/main/java/com/yinlihupo/enlish/service/model/vo/login/VerificationCodeReqVO.java b/enlish-service/src/main/java/com/yinlihupo/enlish/service/model/vo/login/VerificationCodeReqVO.java new file mode 100644 index 0000000..6c43c7e --- /dev/null +++ b/enlish-service/src/main/java/com/yinlihupo/enlish/service/model/vo/login/VerificationCodeReqVO.java @@ -0,0 +1,15 @@ +package com.yinlihupo.enlish.service.model.vo.login; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@NoArgsConstructor +@Data +@Builder +public class VerificationCodeReqVO { + + private String phone; +} diff --git a/enlish-service/src/main/java/com/yinlihupo/enlish/service/service/LoginService.java b/enlish-service/src/main/java/com/yinlihupo/enlish/service/service/LoginService.java new file mode 100644 index 0000000..3507dac --- /dev/null +++ b/enlish-service/src/main/java/com/yinlihupo/enlish/service/service/LoginService.java @@ -0,0 +1,8 @@ +package com.yinlihupo.enlish.service.service; + +public interface LoginService { + + void login(String phone, String name, String reqPassword, String reqCode); + + void sendVerificationCode(String phone); +} diff --git a/enlish-service/src/main/java/com/yinlihupo/enlish/service/service/login/LoginServiceImpl.java b/enlish-service/src/main/java/com/yinlihupo/enlish/service/service/login/LoginServiceImpl.java new file mode 100644 index 0000000..ca0c7a2 --- /dev/null +++ b/enlish-service/src/main/java/com/yinlihupo/enlish/service/service/login/LoginServiceImpl.java @@ -0,0 +1,108 @@ +package com.yinlihupo.enlish.service.service.login; + +import cn.dev33.satoken.stp.StpUtil; +import cn.hutool.core.util.RandomUtil; +import com.aliyun.dysmsapi20170525.Client; +import com.aliyun.dysmsapi20170525.models.SendSmsRequest; +import com.aliyun.dysmsapi20170525.models.SendSmsResponse; +import com.aliyun.teautil.models.RuntimeOptions; +import com.yinlihupo.enlish.service.constant.UserRedisConstants; +import com.yinlihupo.enlish.service.domain.dataobject.UserDO; +import com.yinlihupo.enlish.service.domain.mapper.UserDOMapper; +import com.yinlihupo.enlish.service.service.LoginService; +import com.yinlihupo.framework.common.util.JsonUtils; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; + +import java.time.Duration; + +@Service +@Slf4j +public class LoginServiceImpl implements LoginService { + + @Resource + private UserDOMapper userDOMapper; + @Resource + private PasswordEncoder passwordEncoder; + @Resource + private StringRedisTemplate stringRedisTemplate; + @Resource + private Client client; + + @Override + public void login(String phone, String name, String reqPassword, String reqCode) { + UserDO userDO = userDOMapper.selectByPhone(phone); + log.info("userDO:{}", userDO); + + String code = stringRedisTemplate.opsForValue().get(UserRedisConstants.buildUserLoginCode(phone)); + if (userDO == null) { + if (code == null || !code.equals(reqCode)) { + throw new RuntimeException("验证码错误"); + } + userDO = UserDO.builder() + .phone(phone) + .name(name) + .password(passwordEncoder.encode(reqPassword)) + .build(); + userDOMapper.insert(userDO); + StpUtil.login(userDO.getId()); + return; + } + + if (code != null && code.equals(reqCode)) { + StpUtil.login(userDO.getId()); + return; + } + + if (reqPassword != null && passwordEncoder.matches(reqPassword, userDO.getPassword())) { + StpUtil.login(userDO.getId()); + throw new RuntimeException("密码错误"); + + } + + throw new RuntimeException("登录错误"); + } + + @Override + public void sendVerificationCode(String phone) { + String code = RandomUtil.randomNumbers(6); + stringRedisTemplate.opsForValue().set(UserRedisConstants.buildUserLoginCode(phone), code, Duration.ofSeconds(60)); + String signName = "短信测试"; + String templateCode = "SMS_154950909"; + String templateParam = String.format("{\"code\":\"%s\"}", code); + try { + sendMessage(phone, signName, templateCode, templateParam); + } catch (Exception e) { + log.error("==> 短信发送失败, phone: {}, signName: {}, templateCode: {}, templateParam: {}", phone, signName, templateCode, templateParam); + throw new RuntimeException(e); + } + } + + private void initUserRole(UserDO userDO) { + // todo + } + + /** + * 发送短信 + */ + public void sendMessage(String signName, String templateCode, String phone, String templateParam) throws Exception { + SendSmsRequest sendSmsRequest = new SendSmsRequest() + .setSignName(signName) + .setTemplateCode(templateCode) + .setPhoneNumbers(phone) + .setTemplateParam(templateParam); + RuntimeOptions runtime = new RuntimeOptions(); + + + log.info("==> 开始短信发送, phone: {}, signName: {}, templateCode: {}, templateParam: {}", phone, signName, templateCode, templateParam); + + // 发送短信 + SendSmsResponse response = client.sendSmsWithOptions(sendSmsRequest, runtime); + + log.info("==> 短信发送成功, response: {}", JsonUtils.toJsonString(response)); + + } +} diff --git a/enlish-service/src/main/resources/config/application-dev.yml b/enlish-service/src/main/resources/config/application-dev.yml index a69c256..99f6bd8 100644 --- a/enlish-service/src/main/resources/config/application-dev.yml +++ b/enlish-service/src/main/resources/config/application-dev.yml @@ -34,4 +34,8 @@ tmp: ai: key: app-loC6IrJpj4cS54MAYp73QtGl - url: https://chat.cosonggle.com/v1/chat-messages \ No newline at end of file + url: https://chat.cosonggle.com/v1/chat-messages + +aliyun: + accessKeyId: + accessKeySecret: \ No newline at end of file diff --git a/enlish-service/src/main/resources/config/application.yml b/enlish-service/src/main/resources/config/application.yml index 0fce1b8..b314786 100644 --- a/enlish-service/src/main/resources/config/application.yml +++ b/enlish-service/src/main/resources/config/application.yml @@ -7,4 +7,23 @@ spring: mybatis: # MyBatis xml 配置文件路径 - mapper-locations: classpath:/mapper/**/*.xml \ No newline at end of file + mapper-locations: classpath:/mapper/**/*.xml + +############## Sa-Token 配置 (文档: https://sa-token.cc) ############## +sa-token: + # token 名称(同时也是 cookie 名称) + token-name: Authorization + # token 前缀 + token-prefix: Bearer + # token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik) + token-style: random-128 + # token 有效期(单位:秒) 默认30天,-1 代表永久有效 + timeout: 2592000 + # token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结 + active-timeout: -1 + # 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录) + is-concurrent: true + # 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token) + is-share: true + # 是否输出操作日志 + is-log: true \ No newline at end of file diff --git a/enlish-service/src/main/resources/generatorConfig.xml b/enlish-service/src/main/resources/generatorConfig.xml index 0cea3ea..5c71e0d 100644 --- a/enlish-service/src/main/resources/generatorConfig.xml +++ b/enlish-service/src/main/resources/generatorConfig.xml @@ -45,7 +45,7 @@ targetProject="src/main/java"/> - + + + + + + + + + + + + + + + insert into user (password, phone, name) + values (#{password}, #{phone}, #{name}) + + + + + \ No newline at end of file diff --git a/enlish-vue/package-lock.json b/enlish-vue/package-lock.json index ff50e31..6a009a6 100644 --- a/enlish-vue/package-lock.json +++ b/enlish-vue/package-lock.json @@ -8,12 +8,14 @@ "name": "enlish-vue", "version": "0.0.0", "dependencies": { + "@vueuse/integrations": "^14.1.0", "axios": "^1.13.2", "echarts": "^6.0.0", "element-plus": "^2.12.0", "flowbite": "^1.8.1", "nprogress": "^0.2.0", "pinia": "^3.0.4", + "universal-cookie": "^8.0.1", "vue": "^3.5.24", "vue-router": "^4.6.4" }, @@ -1200,6 +1202,116 @@ } } }, + "node_modules/@vueuse/integrations": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-14.1.0.tgz", + "integrity": "sha512-eNQPdisnO9SvdydTIXnTE7c29yOsJBD/xkwEyQLdhDC/LKbqrFpXHb3uS//7NcIrQO3fWVuvMGp8dbK6mNEMCA==", + "license": "MIT", + "dependencies": { + "@vueuse/core": "14.1.0", + "@vueuse/shared": "14.1.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "async-validator": "^4", + "axios": "^1", + "change-case": "^5", + "drauu": "^0.4", + "focus-trap": "^7", + "fuse.js": "^7", + "idb-keyval": "^6", + "jwt-decode": "^4", + "nprogress": "^0.2", + "qrcode": "^1.5", + "sortablejs": "^1", + "universal-cookie": "^7 || ^8", + "vue": "^3.5.0" + }, + "peerDependenciesMeta": { + "async-validator": { + "optional": true + }, + "axios": { + "optional": true + }, + "change-case": { + "optional": true + }, + "drauu": { + "optional": true + }, + "focus-trap": { + "optional": true + }, + "fuse.js": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "jwt-decode": { + "optional": true + }, + "nprogress": { + "optional": true + }, + "qrcode": { + "optional": true + }, + "sortablejs": { + "optional": true + }, + "universal-cookie": { + "optional": true + } + } + }, + "node_modules/@vueuse/integrations/node_modules/@types/web-bluetooth": { + "version": "0.0.21", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", + "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==", + "license": "MIT" + }, + "node_modules/@vueuse/integrations/node_modules/@vueuse/core": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-14.1.0.tgz", + "integrity": "sha512-rgBinKs07hAYyPF834mDTigH7BtPqvZ3Pryuzt1SD/lg5wEcWqvwzXXYGEDb2/cP0Sj5zSvHl3WkmMELr5kfWw==", + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.21", + "@vueuse/metadata": "14.1.0", + "@vueuse/shared": "14.1.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/@vueuse/integrations/node_modules/@vueuse/metadata": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-14.1.0.tgz", + "integrity": "sha512-7hK4g015rWn2PhKcZ99NyT+ZD9sbwm7SGvp7k+k+rKGWnLjS/oQozoIZzWfCewSUeBmnJkIb+CNr7Zc/EyRnnA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/integrations/node_modules/@vueuse/shared": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-14.1.0.tgz", + "integrity": "sha512-EcKxtYvn6gx1F8z9J5/rsg3+lTQnvOruQd8fUecW99DCK04BkWD7z5KQ/wTAx+DazyoEE9dJt/zV8OIEQbM6kw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, "node_modules/@vueuse/metadata": { "version": "9.13.0", "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-9.13.0.tgz", @@ -1305,7 +1417,8 @@ "version": "4.2.5", "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz", "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/asynckit": { "version": "0.4.0", @@ -1356,6 +1469,7 @@ "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", "license": "MIT", + "peer": true, "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", @@ -1553,6 +1667,19 @@ "dev": true, "license": "MIT" }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/copy-anything": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-4.0.5.tgz", @@ -2445,7 +2572,8 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/nprogress/-/nprogress-0.2.0.tgz", "integrity": "sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/object-assign": { "version": "4.1.1", @@ -3148,6 +3276,16 @@ "@types/estree": "^1.0.0" } }, + "node_modules/universal-cookie": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-8.0.1.tgz", + "integrity": "sha512-B6ks9FLLnP1UbPPcveOidfvB9pHjP+wekP2uRYB9YDfKVpvcjKgy1W5Zj+cEXJ9KTPnqOKGfVDQBmn8/YCQfRg==", + "license": "MIT", + "peer": true, + "dependencies": { + "cookie": "^1.0.2" + } + }, "node_modules/unplugin": { "version": "2.3.11", "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.11.tgz", diff --git a/enlish-vue/package.json b/enlish-vue/package.json index e01ba84..859f16a 100644 --- a/enlish-vue/package.json +++ b/enlish-vue/package.json @@ -9,12 +9,14 @@ "preview": "vite preview" }, "dependencies": { + "@vueuse/integrations": "^14.1.0", "axios": "^1.13.2", "echarts": "^6.0.0", "element-plus": "^2.12.0", "flowbite": "^1.8.1", "nprogress": "^0.2.0", "pinia": "^3.0.4", + "universal-cookie": "^8.0.1", "vue": "^3.5.24", "vue-router": "^4.6.4" }, diff --git a/enlish-vue/src/api/user.js b/enlish-vue/src/api/user.js new file mode 100644 index 0000000..78b7225 --- /dev/null +++ b/enlish-vue/src/api/user.js @@ -0,0 +1,9 @@ +import axios from "@/axios"; + +export function login(data) { + return axios.post("/login/login", data) +} + +export function getVerificationCode(data) { + return axios.post("/login/sendVerificationCode", data) +} diff --git a/enlish-vue/src/axios.js b/enlish-vue/src/axios.js index 484858b..c4e2f80 100644 --- a/enlish-vue/src/axios.js +++ b/enlish-vue/src/axios.js @@ -1,4 +1,6 @@ import axios from "axios"; +import { getToken } from "@/composables/auth"; +import { showMessage } from "@/composables/util.js"; // 创建 Axios 实例 const instance = axios.create({ @@ -6,5 +8,34 @@ const instance = axios.create({ timeout: 7000, // 请求超时时间 }) +// 添加请求拦截器 +instance.interceptors.request.use(function (config) { + const token = getToken() + console.log('统一添加请求头中的 Token:' + token) + + // 当 token 不为空时 + if (token) { + // 添加请求头, key 为 Authorization,value 值的前缀为 'Bearer ' + config.headers['Authorization'] = 'Bearer ' + token + } + return config; +}, function (error) { + // 对请求错误做些什么 + return Promise.reject(error) +}); + +// 添加响应拦截器 +instance.interceptors.response.use(function (response) { + // 2xx 范围内的状态码都会触发该函数。 + // 对响应数据做点什么 + return response +}, function (error) { + // 若后台有错误提示就用提示文字,默认提示为 '请求失败' + let errorMsg = error.response.data.message || '请求失败' + // 弹错误提示 + showMessage(errorMsg, 'error') + return Promise.reject(error) +}) + // 暴露出去 export default instance; diff --git a/enlish-vue/src/composables/auth.js b/enlish-vue/src/composables/auth.js new file mode 100644 index 0000000..ee85f01 --- /dev/null +++ b/enlish-vue/src/composables/auth.js @@ -0,0 +1,20 @@ +import { useCookies } from '@vueuse/integrations/useCookies' + +// 存储在 Cookie 中的 Token 的 key +const TOKEN_KEY = 'Authorization' +const cookie = useCookies() + +// 获取 Token 值 +export function getToken() { + return cookie.get(TOKEN_KEY) +} + +// 设置 Token 到 Cookie 中 +export function setToken(token, expires = 2592000) { + return cookie.set(TOKEN_KEY, token, { expires }) +} + +// 删除 Token +export function removeToken() { + return cookie.remove(TOKEN_KEY) +} diff --git a/enlish-vue/src/layouts/components/Header.vue b/enlish-vue/src/layouts/components/Header.vue index 27a7a44..cfa9a4d 100644 --- a/enlish-vue/src/layouts/components/Header.vue +++ b/enlish-vue/src/layouts/components/Header.vue @@ -7,12 +7,14 @@ Flowbite
+ + Login + Log - in - Get - started + class="text-white bg-primary-700 hover:bg-primary-800 focus:ring-4 focus:ring-primary-300 font-medium rounded-lg text-sm px-4 lg:px-5 py-2 lg:py-2.5 mr-2 dark:bg-primary-600 dark:hover:bg-primary-700 focus:outline-none dark:focus:ring-primary-800"> + Get started +
+ diff --git a/enlish-vue/src/layouts/components/LoginDialog.vue b/enlish-vue/src/layouts/components/LoginDialog.vue new file mode 100644 index 0000000..a16cfd1 --- /dev/null +++ b/enlish-vue/src/layouts/components/LoginDialog.vue @@ -0,0 +1,288 @@ + + + + + diff --git a/pom.xml b/pom.xml index 07c8ca6..e73f516 100644 --- a/pom.xml +++ b/pom.xml @@ -40,7 +40,7 @@ 2.16.1 8.0.29 1.2.23 - 1.38.0 + 1.44.0 33.0.0-jre 5.8.26 3.12.0 @@ -209,14 +209,14 @@ cn.dev33 sa-token-spring-boot3-starter - ${sa-token.version} + ${sa-token} - + cn.dev33 - sa-token-redis-jackson - ${sa-token.version} + sa-token-redis-template + ${sa-token} @@ -248,6 +248,32 @@ activation ${activation.version} + + + + com.aliyun + dysmsapi20170525 + ${dysmsapi.version} + + + + io.jsonwebtoken + jjwt + 0.9.1 + + + + com.auth0 + java-jwt + 4.4.0 + + + + commons-codec + commons-codec + 1.15 + + org.glassfish.jaxb