亿级高并发电商项目-- 实战篇 --万达商城项目 十二(编写用户服务、发送短信功能、发送注册验证码功能、手机号验证码登录功能、单点登录等模块)

news2025/2/27 20:15:16

 

 

👏作者简介:大家好,我是小童,Java开发工程师,CSDN博客博主,Java领域新星创作者
📕系列专栏:前端、Java、Java中间件大全、微信小程序、微信支付、若依框架、Spring全家桶
📧如果文章知识点有错误的地方,请指正!和大家一起学习,一起进步👀
🔥如果感觉博主的文章还不错的话,请👍三连支持👍一下博主哦
🍂博主正在努力完成2023计划中:以梦为马,扬帆起航,2023追梦人 

专栏:高并发项目 

编写用户服务接口

在通用模块编写用户服务接口:

/**
* 商城用户服务
*/
public interface ShoppingUserService {
    // 注册时向redis保存手机号+验证码
    void saveRegisterCheckCode(String phone,String checkCode);
    // 注册时验证手机号
    void registerCheckCode(String phone,String checkCode);
    // 用户注册
    void register(ShoppingUser shoppingUser);
    // 用户名密码登录
    String loginPassword(String username,String password);
    // 登录时向redis保存手机号+验证码
    void saveLoginCheckCode(String phone,String checkCode);
    // 手机号验证码登录
    String loginCheckCode(String phone, String checkCode);   
    // 获取登录用户名
    String getName(String token);
    // 获取登录用户
    ShoppingUser getLoginUser(String token);
}

创建网站用户服务模块

1、创建名为 shopping_user_service 的SpringBoot工程,添加相关依赖。

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <!-- MyBatisPlus -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.5.0</version>
    </dependency>
    <!-- mysql驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>com.itbaizhan</groupId>
        <artifactId>shopping_common</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </dependency>
    <!-- dubbo -->
    <dependency>
        <groupId>org.apache.dubbo</groupId>
        <artifactId>dubbo-spring-boot-starter</artifactId>
        <version>2.7.8</version>
    </dependency>
    <!-- 操作zookeeper -->
    <dependency>
        <groupId>org.apache.curator</groupId>
        <artifactId>curator-recipes</artifactId>
        <version>4.2.0</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <!-- redis -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
</dependencies>

 2、设置该工程的父工程为 shopping 。

<parent>
    <groupId>com.ittxc</groupId>
    <artifactId>shopping</artifactId>
    <version>1.0-SNAPSHOT</version>
</parent>

3、给 shopping 工程设置子模块

<!-- 子模块 -->
<modules>
    <!-- 用户服务 -->
    <module>shopping_user_service</module>
</modules>

4、编写配置文件 application.yml

# 端口号
server:
 port: 9006
 # 日志格式
logging:
 pattern:
   console: '%d{HH:mm:ss.SSS} %clr(%-5level) --- [%-15thread] %cyan(%-50logger{50}):%msg%n'
#配置mybatis-plus
mybatis-plus:
 global-config:
   db-config:
      # 表名前缀
     table-prefix: bz_
      # 主键生成策略为自增
     id-type: auto
 configuration:
    # 关闭列名自动驼峰命名规则映射
   map-underscore-to-camel-case: false
   log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #开启sql日志
spring:
  # 数据源
 datasource:
   driver-class-name: com.mysql.cj.jdbc.Driver
   url: jdbc:mysql:///baizhanshopping?serverTimezone=UTC
   username: root
   password01: 123456
  # redis
 redis:
   host: 192.168.100.131
   port: 6379
   timeout: 30000
   jedis:
     pool:
       max-active: 8
       max-wait: -1
       max-idle: 8
       min-idle: 0
dubbo:
 application:
   name: shopping_user_service # 项目名
 registry:
   address: zookeeper://192.168.100.131 #注册中心地址
   port: 2181       # 注册中心的端口
   timeout: 10000 # 注册到zk上超时时间,ms
 protocol:
   name: dubbo # dubbo使用的协议
   port: -1 # dubbo自动分配端口
 scan:
   base-packages: com.ittxc.shopping_user_service.service # 包扫描

创建网站用户Api模块

1、创建名为 shopping_user_customer_api 的SpringBoot工程,添加相关依赖。

<dependencies>
    <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
       <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- dubbo -->
    <dependency>
        <groupId>org.apache.dubbo</groupId>
        <artifactId>dubbo-spring-boot-starter</artifactId>
        <version>2.7.8</version>
    </dependency>
    <!-- 操作zookeeper -->
    <dependency>
        <groupId>org.apache.curator</groupId>
        <artifactId>curator-recipes</artifactId>
        <version>4.2.0</version>
    </dependency>
    <dependency>
        <groupId>com.itbaizhan</groupId>
        <artifactId>shopping_common</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

 2、设置该工程的父工程为 shopping 。

<parent>
    <groupId>com.ittxc</groupId>
    <artifactId>shopping</artifactId>
    <version>1.0-SNAPSHOT</version>
</parent>

3、给 shopping 工程设置子模块

<!-- 子模块 -->
<modules>
    <!-- 用户业务的api -->
  <module>shopping_user_customer_api</module>
</modules>

4、编写配置文件 application.yml

# 端口号
server:
 port: 8003
# 日志格式
logging:
  pattern:
    console: '%d{HH:mm:ss.SSS} %clr(%-5level) --- [%-15thread] %cyan(%-50logger{50}):%msg%n'
dubbo:
 application:
   name: shopping_user_customer_api # 项目名
 registry:
   address: zookeeper://192.168.100.131 #注册中心地址
   port: 2181       # 注册中心的端口
   timeout: 10000 # 注册到zk上超时时间,ms
 protocol:
   name: dubbo # dubbo使用的协议
   port: -1 # dubbo自动分配端口

5、启动类忽略数据源自动配置

@SpringBootApplication(exclude= {DataSourceAutoConfiguration.class})
public class ShoppingUserCustomerApiApplication {
    public static void main(String[] args)
      {
        SpringApplication.run(ShoppingUserCustomerApiApplication.class, args);
   }
}

用户注册的步骤

在用户注册时,我们要保证用户输入的手机号就是他本人使用的手 机号,方便后期的广告推送、安全认证等。所以在注册前,会向用 户手机发送一个四位随机数验证码,如果用户能获取到该验证码, 证明该手机就是用户本人使用。用户注册的步骤如下:

申请阿里短信服务 

1、访问阿里云 https://www.aliyun.com/,完成登录

2、进入短信服务 

3、开通短信服务 

4、购买短信条数 

 5、购买完成进入阿里云短信控制台https://dysms.console.aliyun.c om/overview

6、绑定测试手机号 

 7、点击调用API发送短信,可以看到发送短信的JAVA代码。

8、申请阿里云秘钥,该秘钥在发送短信时会作为参数传入 

编写发送短信功能 

1、创建名为 shopping_message_service 的SpringBoot工程,添加相关依赖。

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
<dependency>
        <groupId>com.ittxc</groupId>
        <artifactId>shopping_common</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </dependency>
    <!-- 阿里短信平台 -->
    <dependency>
        <groupId>com.aliyun</groupId>
        <artifactId>dysmsapi20170525</artifactId>
        <version>2.0.9</version>
    </dependency>
    <!-- dubbo -->
    <dependency>
        <groupId>org.apache.dubbo</groupId>
        <artifactId>dubbo-spring-boot-starter</artifactId>
        <version>2.7.8</version>
    </dependency>
    <!-- 操作zookeeper -->
    <dependency>
        <groupId>org.apache.curator</groupId>
        <artifactId>curator-recipes</artifactId>
        <version>4.2.0</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

2、设置该工程的父工程为 shopping 。

<parent>
    <groupId>com.ittxc</groupId>
    <artifactId>shopping</artifactId>
    <version>1.0-SNAPSHOT</version>
</parent>

3、给 shopping 工程设置子模块

<!-- 子模块 -->
<modules>
    <!-- 短信服务 -->
  <module>shopping_message_service</module>
</modules>

4、编写配置文件 application.yml

# 端口号
server:
 port: 9007
# 日志格式
logging:
 pattern:
     console: '%d{HH:mm:ss.SSS} %clr(%-5level) --- [%-15thread]
%cyan(%-50logger{50}):%msg%n'
dubbo:
 application:
   name: shopping_message_service # 项目名
 registry:
   address: zookeeper://192.168.100.131 #注册中心地址
   port: 2181       # 注册中心的端口
   timeout: 10000 # 注册到zk上超时时间,ms
 protocol:
   name: dubbo # dubbo使用的协议
   port: -1 # dubbo自动分配端口
 scan:
   base-packages: com.ittxc.shopping_message_service.service # 包扫描
message:
 accessKeyId: LTAI5tBUnRMTgKmRR92yxrFf
 accessKeySecret: RN7umfy3mMA2xlGa8rP0IzwQdTc6Pb

5、启动类忽略数据源自动配置

@SpringBootApplication(exclude= {DataSourceAutoConfiguration.class})
public class ShoppingMessageServiceApplication {
    public static void main(String[] args)
      {
         SpringApplication.run(ShoppingMessageServiceApplication.class, args);
   }
}

6、在通用模块编写发送短信服务接口

/**
* 短信服务
*/
public interface MessageService {
    /**
     * 发送短信
     * @param phoneNumber 手机号
     * @param code 验证码
     * @return 返回结果
     */
    BaseResult sendMessage(String phoneNumber,String code);
}

7、在短信服务模块编写发送短信实现类

@DubboService
public class MessageServiceImpl implements
MessageService {
    @Value("${message.accessKeyId}")
    private String accessKeyId;
    @Value("${message.accessKeySecret}")
    private String accessKeySecret;
    /**
     * 使用AK&SK初始化账号Client
     * @param accessKeyId
     * @param accessKeySecret
     * @return Client
     * @throws Exception
     */
    @SneakyThrows
    private Client createClient(String accessKeyId, String accessKeySecret) {
        Config config = new Config()
               .setAccessKeyId(accessKeyId)
               .setAccessKeySecret(accessKeySecret);
        // 访问的域名
        config.endpoint = "dysmsapi.aliyuncs.com";
        return new Client(config);
   }
    @SneakyThrows
    @Override
    public BaseResult sendMessage(String phoneNumber,String code) {
        Client client = createClient(accessKeyId,accessKeySecret);
        SendSmsRequest sendSmsRequest = new SendSmsRequest()
               .setSignName("阿里云短信测试")
               .setTemplateCode("SMS_154950909")
               .setPhoneNumbers(phoneNumber)
               .setTemplateParam("{\"code\":\""+code+"\"}");
        RuntimeOptions runtime = new RuntimeOptions();
        // 复制代码运行请自行打印API的返回值
        SendSmsResponse sendSmsResponse = client.sendSmsWithOptions(sendSmsRequest,
runtime);
        SendSmsResponseBody body = sendSmsResponse.getBody();
        if ("OK".equals(body.getCode())){
            return new BaseResult(200,body.getCode(),body.getMessage());
       }else{
            return new BaseResult(500,body.getCode(),body.getMessage());
       }
   }
}

8、测试该方法

编写发送注册验证码功能

1、在通用模块编写生成随机数的工具类

public class RandomUtil {
    /**
     * 生成验证码
     * @param digit 位数
     * @return
     */
    public static String buildCheckCode(int digit){
        String str = "0123456789";
        StringBuilder sb = new StringBuilder();
        Random random = new Random();
        for (int i = 0; i < digit; i++) {
            char ch = str.charAt(random.nextInt(str.length()));
            sb.append(ch);
       }
        return sb.toString();
   }
}

2、在用户服务模块编写用户服务接口实现类

@DubboService
public class ShoppingUserServiceImpl implements ShoppingUserService {
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
      private ShoppingUserMapper shoppingUserMapper;
    @Override
    public void saveRegisterCheckCode(String phone, String checkCode) {
        ValueOperations valueOperations = redisTemplate.opsForValue();
        // redis键为手机号,值为验证码,过期时间5分钟
        valueOperations.set("registerCode:" + phone, checkCode, 300, TimeUnit.SECONDS);
   }
}

3、在用户API模块编写控制器

/**
* 商城用户
*/
@RestController
@RequestMapping("/shoppingUser")
public class ShoppingUserController {
    @DubboReference
    private ShoppingUserService shoppingUserService;
    /**
     * 发送注册短信
     * @param phone 注册手机号
     * @return 操作结果
     */
     @GetMapping("/sendMessage")
    public BaseResult sendMessage(String phone){
        // 1.生成随机四位数
        String checkCode = RandomUtil.buildCheckCode(4);
        // 2.发送短信
        BaseResult result = messageService.sendMessage(phone,checkCode);
        // 3.发送成功,将验证码保存到redis中,发送失败,返回发送结果
        if (200 == result.getCode()) {
           shoppingUserService.saveRegisterCheckCode(phone, checkCode);
            return BaseResult.ok();
       } else {
            return result;
       }
   }
}

4、测试控制器

编写验证注册验证码功能

1、在用户服务模块编写用户服务接口实现类

@Override
public void registerCheckCode(String phone, String checkCode) {
    // 验证验证码
    ValueOperations valueOperations = redisTemplate.opsForValue();
    Object checkCodeRedis = valueOperations.get("registerCode:" + phone);
    if (!checkCode.equals(checkCodeRedis))
      {
        throw new BusException(CodeEnum.REGISTER_CODE_ERROR);
   }
}

2、在用户API模块编写控制器

/**
  * 验证用户注册验证码
  * @param phone 手机号
  * @param checkCode 验证码
  * @return 200验证成功,605验证码不正确
  */
@GetMapping("/registerCheckCode")
public BaseResult register(String phone,String checkCode){
   shoppingUserService.registerCheckCode(phone,checkCode);
    return BaseResult.ok();
}

3、测试控制器

编写用户注册功能

1、在通用模块编写MD5加密工具类,用于给用户密码进行加密和验证

public class Md5Util {
    public final static String md5key = "BAIZHAN"; // 秘钥
    /**
     * 加密
     * @param text 明文
     * @return 密文
     */
    public static String encode(String text){
        return DigestUtils.md5Hex(text + md5key);
   }
    /**
     * 验证
     *
     * @param text 明文
     * @param cipher 密文
     * @return true/false
     */
    public static boolean verify(String text, String cipher){
        // 将明文转为密文进行比对
        String md5Text = encode(text);
        if (md5Text.equalsIgnoreCase(cipher)) {
            return true;
       }
        return false;
   }
}

2、在用户服务模块编写用户服务接口实现类

@Override
public void register(ShoppingUser
shoppingUser) {
    // 1.验证手机号是否存在
    String phone = shoppingUser.getPhone();
    QueryWrapper<ShoppingUser> queryWrapper = new QueryWrapper();
    queryWrapper.eq("phone", phone);
    List<ShoppingUser> shoppingUsers = shoppingUserMapper.selectList(queryWrapper);
    if (shoppingUsers != null && shoppingUsers.size() > 0) {
        throw new BusException(CodeEnum.REGISTER_REPEAT_PHONE_ERROR);
   }
    // 2.验证用户名是否存在
    String username = shoppingUser.getUsername();
    QueryWrapper<ShoppingUser> queryWrapper1 = new QueryWrapper();
    queryWrapper1.eq("username",username);
    List<ShoppingUser> shoppingUsers1 = shoppingUserMapper.selectList(queryWrapper1);
    if (shoppingUsers1 != null && shoppingUsers1.size() > 0) {
        throw new BusException(CodeEnum.REGISTER_REPEAT_NAME_ERROR);
   }
    // 3.新增用户
    shoppingUser.setStatus("Y");
    shoppingUser.setPassword(Md5Util.encode(shoppingUser.getPassword()));
    shoppingUserMapper.insert(shoppingUser);
}

3、在用户API模块编写控制器

/**
  * 用户注册
  * @param shoppingUser 用户信息
  * @return 注册结果
  */
@PostMapping("/register")
public BaseResult register(@RequestBody ShoppingUser shoppingUser){
    shoppingUserService.register(shoppingUser);
    return BaseResult.ok();
}

4、测试控制器

编写用户名密码登录功能

1、在用户服务模块编写用户服务接口实现类

@Override
public String loginPassword(String username, String password) {
    QueryWrapper<ShoppingUser> queryWrapper = new QueryWrapper();
    queryWrapper.eq("username", username);
    ShoppingUser shoppingUser = shoppingUserMapper.selectOne(queryWrapper);
    // 验证用户名
    if (shoppingUser == null) {
       throw new BusException(CodeEnum.LOGIN_NAME_PASSWORD_ERROR);
   }
    // 验证密码
    boolean verify = Md5Util.verify(password,shoppingUser.getPassword());
    if (!verify) {
        throw new BusException(CodeEnum.LOGIN_NAME_PASSWORD_ERROR);
   }
    // 返回用户名
    return username;
}

2、在用户API模块编写控制器

/**
  * 用户名密码登录
  * @param shoppingUser 用户对象
  * @return 登录结果
  */
@PostMapping("/loginPassword")
public BaseResult loginPassword(@RequestBody ShoppingUser shoppingUser){
    shoppingUserService.loginPassword(shoppingUser.getUsername(),shoppingUser.getPassword());
    return BaseResult.ok();
}

3、测试控制器

编写手机号验证码登录功能

手机号验证码登录的流程为:用户先输入手机号,向手机号发送随 机验证码,并将验证码保存到redis中。用户收到短信后将验证码输入,如果验证码和redis中的验证码匹配成功,证明登录者就是使用 该手机的用户,登录成功。

向用户发送登录验证码 

1、在用户服务模块编写用户服务接口实现类

// 保存登录验证码到redis
@Override
public void saveLoginCheckCode(String phone, String checkCode) {
    ValueOperations valueOperations = redisTemplate.opsForValue();
    // redis键为手机号,值为验证码,过期时间5分钟
    valueOperations.set("loginCode:" + phone, checkCode, 300, TimeUnit.SECONDS);
}

2、在用户API模块编写控制器

/**
  * 发送登录短信验证码
  *
  * @param phone 手机号
  * @return 操作结果
  */
@GetMapping("/sendLoginCheckCode")
public BaseResult sendLoginCheckCode(String phone) {
    // 1.生成随机四位数
    String checkCode = RandomUtil.buildCheckCode(4);
    // 2.发送短信
    BaseResult result = messageService.sendMessage(phone,checkCode);
    // 3.发送成功,将验证码保存到redis中,发送失败,返回发送结果
    if (200 == result.getCode()) {
        shoppingUserService.saveLoginCheckCode(phone, checkCode);
        return BaseResult.ok();
   } else {
        return result;
   }
}

3、测试控制器

验证登录验证码

1、在用户服务模块编写用户服务接口实现类

// 验证登录验证码
@Override
public String loginCheckCode(String phone, String checkCode) {
    ValueOperations valueOperations = redisTemplate.opsForValue();
    Object checkCodeRedis = valueOperations.get("loginCode:" + phone);
    if (!checkCode.equals(checkCodeRedis))
      {
        throw new BusException(CodeEnum.LOGIN_CODE_ERROR);
   }
    // 登录成功,查询用户
    QueryWrapper<ShoppingUser> queryWrapper = new QueryWrapper();
    queryWrapper.eq("phone", phone);
    ShoppingUser shoppingUser = shoppingUserMapper.selectOne(queryWrapper);
    // 返回用户名
    return shoppingUser.getUsername();
}

2、在用户API模块编写控制器

/**
  * 手机号验证码登录
  * @param phone 手机号
  * @param checkCode 验证码
  * @return 登录结果
  */
@PostMapping("/loginCheckCode")
public BaseResult loginCheckCode(String phone, String checkCode){
    shoppingUserService.loginCheckCode(phone,checkCode);
    return BaseResult.ok();
}

3、测试控制器

单点登录的概念

目前登录成功后我们并没有保存用户信息,也没有在访问接口前验 证用户信息。如果使用传统的session保存用户信息,使用filter验证 用户是否登录。存在一个问题:session保存在服务器中,而我们的 系统存在诸多子系统,这些子系统是分别部署在不同的服务器中,互相无法访问(登录后的session保存在用户模块,搜索模块无法访问用户模块的session,无法验证用户是否登录)。此时我们需要使用单点登录技术解决这一问题。

单点登录 

单点登录(Single Sign On)简称为 SSO。即在多个应用系统中, 用户只需要登录一次就可以访问所有相互信任的应用系统。JWT是一种常用的单点登录解决方案。

JWT 

JWT是Json Web Token的简称,是一种令牌生成算法。使用JWT能 够保证Token的安全性,且能够进行Token时效性的检验。 使用JWT时,登录成功后将用户信息生成一串令牌字符串。将该字 符串返回给客户端,客户端每次请求时都在请求头携带该令牌字符串。在其他模块验证令牌,通过则证明用户处于登录状态,并拿到 解析后的用户信息,未通过证明用户处于未登录状态。

编写单点登录功能 

1、在通用模块引入JWT依赖

<!-- JWT -->
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.4.0</version>
</dependency>

2、在通用模块编写JWT工具类

public class JWTUtil {
    //token过期时间,一天
    private static final Long EXPIRE_DATE = 1000*60*60*24L;
    // 秘钥
    private static final String SECRET = "txc";
    // 签发者
    private static final String ISSUER = "TXC";
    /**
     * 签名生成
     * @param shoppingUser
     * @return
     */
    public static String sign(ShoppingUser shoppingUser){
        String token = JWT.create()
               .withIssuer(ISSUER) // 签发者
               .withIssuedAt(new Date())// 签发时间
               .withExpiresAt(new Date(new Date().getTime() + EXPIRE_DATE))// 过期时间
               .withSubject(shoppingUser.getUsername())// 保存用户名
               .sign(Algorithm.HMAC256(SECRET)); // 秘钥
        return token;
 }
    /**
     * 签名解析
     * @param token 签名字符串
     * @return 解析得出的用户名
     */
    public static String verify(String token){
        try {
            String username = JWT
                   .require(Algorithm.HMAC256(SECRET))
                   .withIssuer(ISSUER)
                   .build()
                   .verify(token)
                   .getSubject();
            return username;
       } catch (Exception e){
            throw new BusException(CodeEnum.VERIFY_TOKEN_ERROR);
       }
   }
}

3、登录后生成令牌

@Override
public String loginPassword(String
username, String password) {
    // 1.验证用户名
    QueryWrapper<ShoppingUser> queryWrapper = new QueryWrapper();
    queryWrapper.eq("username",username);
    ShoppingUser shoppingUser = shoppingUserMapper.selectOne(queryWrapper);
    if (shoppingUser == null){
        throw new BusException(CodeEnum.LOGIN_NAME_PASSWORD_ERROR);
   }
    // 2.验证密码
    boolean verify = Md5Util.verify(password, shoppingUser.getPassword());
    if (!verify){
        throw new BusException(CodeEnum.LOGIN_NAME_PASSWORD_ERROR);
   }
    // 3.生成JWT令牌,返回令牌
    String sign = JWTUtil.sign(shoppingUser);
    return sign;
}
@Override
public String loginCheckCode(String phone,String checkCode) {
    ValueOperations valueOperations = redisTemplate.opsForValue();
    Object checkCodeRedis = valueOperations.get("loginCode:" + phone);
    if (!checkCode.equals(checkCodeRedis))
      {
        throw new BusException(CodeEnum.LOGIN_CODE_ERROR);
   }
    // 登录成功,查找用户,返回用户名
    QueryWrapper<ShoppingUser> queryWrapper = new QueryWrapper();
    queryWrapper.eq("phone",phone);
    ShoppingUser shoppingUser = shoppingUserMapper.selectOne(queryWrapper);
    // 生成JWT令牌,返回令牌
    String sign = JWTUtil.sign(shoppingUser);
    return sign;
}

4、将令牌返回给客户端

/**
  * 用户名密码登录
  * @param shoppingUser 用户对象
  * @return 登录结果
  */
@PostMapping("/loginPassword")
public BaseResult loginPassword(@RequestBody ShoppingUser shoppingUser){
  String sign = shoppingUserService.loginPassword(shoppingUser.getUsername(),shoppingUser.getPassword());
    return BaseResult.ok(sign);
}
/**
* 手机号验证码登录
* @param phone 手机号
* @param checkCode 验证码
* @return 登录结果
*/
@PostMapping("/loginCheckCode")
public BaseResult loginCheckCode(Stringphone,String checkCode){
    String sign = shoppingUserService.loginCheckCode(phone,checkCode);
    return BaseResult.ok(sign);
}

编写拦截器验证令牌

在通用模块编写JWT拦截器

// 拦截器,验证令牌
public class JWTInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request,HttpServletResponse response, Objecthandler) throws Exception {
        // 获取请求头中的token
        String token = request.getHeader("token");
        // 验证token
        JWTUtil.verify(token);
        return true;
   }
}

注:请求时要在请求头添加token=令牌数据

配置拦截的接口 

在不同API模块都要配置拦截的接口

1、配置用户API模块拦截的接口

// 拦截器配置
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new JWTInterceptor())
           .addPathPatterns("/**") //拦截的接口
           .excludePathPatterns(
           "/user/shoppingUser/sendMessage",
           "/user/shoppingUser/registerCheckCode",
           "/user/shoppingUser/register",
           "/user/shoppingUser/loginPassword",
           "/user/shoppingUser/sendLoginCheckCode",
           "/user/shoppingUser/loginCheckCode"
       ); //放行的接口
   }
}

2、配置搜索API模块拦截的接口

// 拦截器配置
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
     @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new JWTInterceptor())
               .addPathPatterns("/**") //拦截的接口
               .excludePathPatterns("/user/goodsSearch/autoSuggest"); //放行的接口
   }
}

3、广告用户API模块不拦截

编写获取用户名功能

1、在用户服务模块编写用户服务接口实现类

@Override
public String getName(String token) {
    String name = JWTUtil.verify(token);
    return name;
}
@Override
public ShoppingUser getLoginUser(String token) {
    String username = JWTUtil.verify(token);
    QueryWrapper<ShoppingUser> queryWrapper = new QueryWrapper();
    queryWrapper.eq("username", username);
    ShoppingUser shoppingUser = shoppingUserMapper.selectOne(queryWrapper);
    return shoppingUser;
}

2、在用户API模块编写控制器

/**
  * 获取登录的用户名
  * @param token 令牌
  * @return 用户名
  */
@GetMapping("/getName")
public BaseResult<String> getName(@RequestHeader("token") String token){
    String name = shoppingUserService.getName(token);
    return BaseResult.ok(name);
}

3、使用Postman测试控制器

编写退出登录功能

后端无需编写退出登录功能,前端删除令牌即可退出登录。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/361358.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Leetcode力扣秋招刷题路-0082

从0开始的秋招刷题路&#xff0c;记录下所刷每道题的题解&#xff0c;帮助自己回顾总结 82. 删除排序链表中的重复元素 II 给定一个已排序的链表的头 head &#xff0c; 删除原始链表中所有重复数字的节点&#xff0c;只留下不同的数字 。返回 已排序的链表 。 示例 1&#…

这6个视频剪辑素材库,你一定要知道~

推荐5个免费商用视频素材网站&#xff0c;建议收藏哦&#xff01; 1、菜鸟图库 视频素材下载_mp4视频大全 - 菜鸟图库 网站素材量很大&#xff0c;有设计、图片、音频、视频等超多素材&#xff0c;大部分都能免费下载。视频素材都很高清&#xff0c;有自然、人物、科技、农业…

前端页面开发模块组织结构

模块组织 任何超过 1000 行的 CSS 代码,你都曾经历过这样的体验: 这个 class 到底是什么意思呢?这个 class 在哪里被使用呢?如果我创建一个 xxoo class,会造成冲突吗?Reasonable System for CSS Stylesheet Structure 的目标就是解决以上问题,它不是一个框架,而是通过…

Freemarker快速入门

freemarker提供很多指令用于解析各种类型的数据模型参考地址&#xff1a;http://freemarker.foofun.cn/ref_directives.html一.测试搭建Freemarker的运行环境并进行测试.1.添加Freemarker与SpringBoot的整合包XML <!-- Spring Boot 对结果视图 Freemarker 集成 --> <d…

互斥锁原理

如果有交互的公共数据区域&#xff0c;我们需要让一个进程先执行&#xff0c;一个进程后执行&#xff0c;互斥锁就是用锁的方式让他们的竞争关系变得有序。 临界区问题 临界区是在程序之间有公共数据交互时产生的区域&#xff0c;没有两个进程可以在它们各自的临界区同时执行…

我的 System Verilog 学习记录(1)

引言 技多不压身&#xff0c;准备开始学一些 System Verilog 的东西&#xff0c;充实一下自己&#xff0c;这个专栏的博客就记录学习、找资源的一个过程&#xff0c;希望可以给后来者一些借鉴吧&#xff0c;IC找工作的都加把油&#xff01; 本文是准备先简单介绍一下环境搭建…

C++11智能指针std::shared_ptr介绍及使用

介绍 shared_ptr是一种智能指针(smart pointer)&#xff0c;作用有如同指针&#xff0c;但会记录有多少个shared_ptrs共同指向一个对象。这便是所谓的引用计数(reference counting),比如我们把只能指针赋值给另外一个对象,那么对象多了一个智能指针指向它,所以这个时候引用计数…

洛谷P1125 [NOIP2008 提高组] 笨小猴 C语言/C++

[NOIP2008 提高组] 笨小猴 题目描述 笨小猴的词汇量很小&#xff0c;所以每次做英语选择题的时候都很头疼。但是他找到了一种方法&#xff0c;经试验证明&#xff0c;用这种方法去选择选项的时候选对的几率非常大&#xff01; 这种方法的具体描述如下&#xff1a;假设 maxn\…

【C++】2.类和对象(上)

1.面向过程和面向对象 C语言是面向过程的&#xff0c;关注的是过程&#xff0c;分析出求解问题的步骤&#xff0c;通过函数调用逐步解决问题。C是基于面向对象的&#xff0c;关注的是对象&#xff0c;将一件事情拆分成不同的对象&#xff0c;靠对象之间的交互完成。 2.类的引入…

【发版或上线项目保姆级心得】

第一步&#xff1a;先在正式环境创建数据库/新增表格或者字段 在数据库表中增加字段/表格&#xff0c;不会报错。 但是切记不要过早数据库字段/表格或者删除字段/表格 第二步&#xff1a;修改配置文件 先将正式环境需要的配置给写好&#xff0c;包括但不仅限于数据库配置、…

秋招面试问题整理之机器学习篇

文章目录随机森林在决策树的哪些方面做出了改进随机森林里每棵树的权重不一定会变成什么模型方差和偏差&#xff0c;正则化解决的是方差大还是偏差大的问题正则化的方法总结了解VC维吗svd了解吗随机森林在决策树的哪些方面做出了改进 回答思路&#xff1a; 随机森林和决策树有…

同步syslog日志到服务端

目录结构前言Windows下安装syslog服务端&#xff08;Syslog Watcher Manager&#xff09;Linux下syslog服务端搭建java同步日志代码块日志同步测试前言 系统同步日志到syslog服务器&#xff0c;此文章记录以下内容&#xff1a; Windows下syslog服务端&#xff08;Syslog Watc…

主食吃什么最健康?

又到了订饭的时候了&#xff0c;今天打算吃什么呢&#xff1f;面包&#xff1f;炒饭&#xff1f;面条&#xff1f;还是粥&#xff1f;上面说到的这些都是主食。大家都知道主食能带来很强的饱腹感&#xff0c;而且还是身体最重要、最经济的营养来源。但五谷杂粮&#xff0c;营养…

【项目】好用快搜文档搜索工具

文章目录一、项目分析1、项目调研2、项目需求3、开发环境4、项目知识框架5、项目实现基本理论二、项目设计整体框架设计代码框架设计三、项目实现1、系统工具模块目录遍历2、数据库管理模块2.1、封装数据库管理类(SqliteManager)2.2、封装数据管理类(DataManager)3、扫描模块4、…

紫外线生物素标记Biotin-PEG2-alkyne,UV Cleavable Biotin-PEG2-alkyne

UV Cleavable Biotin-PEG2-alkyne含有紫外线可切割碎片(containsa UV cleavable Fragemnt)&#xff0c;试剂通过点击化学与含叠氮化物的分子反应。点击化学生物素标记试剂包含各种点击化学官能团修饰的生物素&#xff0c;适用于各种生物素标记实验。1.UV Cleavable Biotin-PEG2…

威马汽车:跃马扬鞭未竟,鞍马劳顿难行?

“活下去&#xff0c;像牲口一样地活下去。” 威马汽车创始人、董事长兼CEO沈晖1月在社交媒体上分享的电影台词&#xff0c;已然成为威马近况的真实写照。 来源&#xff1a;新浪微博威马汽车沈晖Freeman 最近&#xff0c;网上出现了大量关于“威马汽车将实施全员停薪留职”的…

【JavaScript速成之路】JavaScript数据类型转换

&#x1f4c3;个人主页&#xff1a;「小杨」的csdn博客 &#x1f525;系列专栏&#xff1a;【JavaScript速成之路】 &#x1f433;希望大家多多支持&#x1f970;一起进步呀&#xff01; 文章目录前言数据类型转换1&#xff0c;转换为字符串型1.1&#xff0c;利用“”拼接转换成…

8、接口与面向接口编程

目录一、接口的基本概念二、类型断言三、面向接口编程一、接口的基本概念 接口的定义&#xff1a;接口是一组行为规范的集合 type Transporter interface { //定义接口&#xff0c;通常接口名以er结尾//接口里面只定义方法&#xff0c;不定义变量move(src string, dest strin…

10.现代循环神经网络

10.现代循环神经网络 目录 门控循环单元&#xff08;GRU&#xff09;门控隐状态 重置门和更新门候选隐状态 隐状态从零开始实现 初始化模型参数定义模型训练与预测 简洁实现总结 长短期记忆网络&#xff08;LSTM&#xff09; 门控记忆元 输入门、忘记门和输出门候选记忆元记忆…

基于xxx开发板的bluez的移植

基于xxx开发板的bluez的移植1.硬件电路2.软件准备2.1.源码配置2.2 编译源码3.请等待《题外话》&#xff1a;刚开始第一次接触bluez&#xff0c;完全没用过&#xff0c;也没搞过&#xff0c;开局一脸懵逼。刚好项目需要用到&#xff0c;只能硬着头皮上&#xff0c;淦淦淦&#x…