java初探之登录终探

Posted lovejune

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java初探之登录终探相关的知识,希望对你有一定的参考价值。

上一章讲了表单验证,数据验证和加密。这一章,将研究服务器和数据库的交互过程。

后端服务器有两种主流的形式,SQL数据库和NOSQL数据库。其中mysql属于SQL数据库,REDIS属于非SQL数据库。先介绍SQL数据库与服务器的交互过程。

 

  • 建立数据库表

CREATE TABLE `user_test` (
  `id` BIGINT(20) NOT NULL COMMENT \'用户ID,手机号码\',
  `password` VARCHAR(32) DEFAULT NULL COMMENT \'MD5(MD5(pass明文+固定salt) + salt)\',
  PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4;

因为是测试,就使用两个字段的用户表。

 

  • 在配置文件中设置数据库的连接 application.properties文件

#DB Configuration:
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/miaosha?useUnicode=true&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=root

springboot号称一键构建,只需要在配置文件里配置了数据库的连接,不需配置任何bean和类,即可完成数据库的操作,即基于注解的配置,当然数据库的CURD操作也可以在xml的MAPPER里完成,但注解方式更为简单,本项目只采用注解的方式。

 

  • 在dao包中编写UserMapper类,完成简单的查询操作

@Repository
@Mapper
public interface UserMapper {

    //根据id查询用户
    @Select("select * from user where id = #{mobile}")
    @Results(id = "userMap" ,value = {
        @Result(id = true,column = "id",property = "mobile"),
            @Result(column = "password",property = "password")}
    )
    public Person userById(String mobile);

}
  • 改写UserService类,完成从数据库查询到登录的操作

//3.根据用户手机查询用户
    public Person personByMobile(String mobile){
        return userMapper.userById(mobile);
    }

 

到目前为止,已经完成了从数据库到服务器的交互过程。但是如果每次查询操作到要与数据库连接,由于延迟,会造成很大的阻塞,因此采用REDIS技术,将数据缓存到内存中,每次查询,只要查缓存中的数据即可,会快很多。

 

 


 

 

下面研究REDIS技术

使用自带的redis模板,不需要创建redis的配置文件,只需要在application.properties中配置

 

  • 配置application.properties

#Redis Configuration:
spring.redis.host=localhost
spring.redis.port=6379

 

  • 需要导入的坐标

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

 

导入坐标之后,可以对RedisTemplate直接注入,并使用其方法,但RedisTemplate的注入需要注意几点:

1.当private RedisTemplate<String ,String > redisTemplate;时,即key与value都是基础类型时,注入只需要@Autowired即可成功。

但当value值是自己定义的类时,注入需要先将该类序列化,即继承序列化接口,然后使用@Resource注解进行注入。即可成功。

 

  • 重写UserService类

 public Result login(Person person){
        if(person==null){
            throw new GobalException(0,"该用户为空");
        }

        String mobile=person.getMobile();
        String password=person.getPassword();

        //根据id得到数据库用户
        Person persondb = personByMobile(mobile);
        if(persondb==null){
            throw new GobalException(0,"查无此人");
        }

        String dbPassword = persondb.getPassword();
        String formPass = Md5Util.dbPass2FormPass(dbPassword);

        if(!formPass.equals(password)){
            throw new GobalException(0,"密码错误");
        }

        return Result.SUCCESS;


    }





    //3.根据用户手机查询用户
    public Person personByMobile(String mobile){

        Person person = redisTemplate.boundValueOps(mobile).get();
        if(person!=null){
            return person;
        }
        person=userMapper.userById(mobile);
        if(person!=null){
            redisTemplate.boundValueOps(mobile).set(person);
        }
        return person;
    }

 

  • 测试

 

 

两次查询,第一次从数据库查询,以后就从缓存上查询。

 

 


 

以上我们的登陆场景的代码差不多分析完了,但为了扩展性,对redis的key值进行分析,在网站的未来,不止有用户,还有商品等各种,对key值得管理,不能随意的取名造成混乱,因此,我们需要抽象出一个专门key的类来。

 

  • 创建一个key的接口

 

public interface KeyPrefix {

    int expireSeconds();//过期时间

    String getPrefix();//key值

}

 

  • 抽象一个key的基本抽象类

public abstract class BasePrefix implements KeyPrefix{

    private int expireSeconds;

    private String prefix;


    public BasePrefix(String prefix){
        this(0,prefix);
    }

    public BasePrefix(int expireSeconds,String prefix){
        this.expireSeconds=expireSeconds;
        this.prefix=prefix;
    }



    public int expireSeconds() {
        return expireSeconds;
    }


    public String getPrefix() {
        String className=getClass().getSimpleName();
        return className+":"+prefix;
    }
}

调用getPrefix()即可得到类加上前缀的key值。

 

  • 创建UserKey值

public class UserKey extends BasePrefix {

    public static final int TOKEN_EXPIRE=3600*24*2;

    public UserKey(int expireSeconds,String prefix) {
        super(expireSeconds,prefix);
    }

    public static UserKey token=new UserKey(TOKEN_EXPIRE,"tk");

    public static UserKey getById=new UserKey(0,"id");
}

 

 至此,我们创建了一个Key值得抽象功能,如果有商品的数据需要存入Redis,则可以再创建一个GoodKey类即可。

 

接下来,需要创建一个redis的service类,来对数据进行一定的操作,该服务类需要有复用功能,因此用到泛型的概念,我们要通过key值取到自己想得到的类,那么不能直接存储对象本身,而是将对象序列化为一个字符串(通常是一个json字符串),然后存入redis中,取出后,再还原对象。

 

  • 创建RedisService

@Service
public class RedisService {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;


    //1.把字符串转换为bean对象
    public static <T> T stringToBean(String str, Class<T> clazz) {
        ObjectMapper objectMapper = new ObjectMapper();
        T t = null;
        if(str==null || str.length()==0){
            return t;
        }
        try {
            t = objectMapper.readValue(str, clazz);
        } catch (Exception e) {
            throw new GobalException(0,"REDIS字符串转换异常");
        }
        return t;
    }

    //2.将bean对象转换为string
    public static <T> String beanToString(T value) {
        ObjectMapper objectMapper = new ObjectMapper();
        String json = null;
        if(value==null){
            return json;
        }
        try {
            json = objectMapper.writeValueAsString(value);

        } catch (JsonProcessingException e) {
            throw new GobalException(0,"REDIS字符串转换异常");
        }
        return json;
    }


    //3.获取对象
    public <T> T get(KeyPrefix prefix, String key,  Class<T> clazz){
        //真正的key
        String realKey=prefix.getPrefix()+key;
        String str = redisTemplate.opsForValue().get(realKey);
        T t = stringToBean(str, clazz);
        return t;
    }

    //4.设置对象
    public <T> void set(KeyPrefix prefix, String key, T value) {
        String str = beanToString(value);
        String realKey=prefix.getPrefix()+key;

        redisTemplate.opsForValue().set(realKey,str);
    }

    //5.判断key值是否存在

    public <T> boolean exists(KeyPrefix prefix, String key) {

        String realKey  = prefix.getPrefix() + key;
        return redisTemplate.hasKey(realKey);
    }

    //6.增加值
    public <T> Long incr(KeyPrefix prefix, String key) {
        String realKey  = prefix.getPrefix() + key;
        return redisTemplate.opsForValue().increment(realKey, 1);
    }

    //7.减少值
    public <T> Long decr(KeyPrefix prefix, String key) {
        String realKey  = prefix.getPrefix() + key;
        return redisTemplate.opsForValue().increment(realKey, -1);
    }

    //8.扫描key值
//    public List<String> scanKeys(String key) {
//
//    }

    //9.删除key值
    public void delete(KeyPrefix prefix, String key) {
        String realKey=prefix.getPrefix()+key;
        redisTemplate.delete(realKey);
    }
}

这里应用了泛型的思想,可以保存任何类型的值,思想是,利用JSON类,将某个实体类转换为JSON字符串,然后将字符串存到REDIS中。

 


到此为止,登录场景所用到的技术和编码工作全部完成,总结如下;

  1. 抽象:将某些属性抽象出来,可以增加复用性,降低耦合,比如Key类。
  2. 安全:登录时的密码需要加密,然后在网路中传输。
  3. 整合:使用框架提供的工具对MVC进行整合,最终完成项目。

下一章将对登录场景的编码进行进一步改进,包括在页面的验证,返回消息的抽象,以及使用Jedis的原生方法完成Redis的缓存操作。

 

 

 

以上是关于java初探之登录终探的主要内容,如果未能解决你的问题,请参考以下文章

java初探之登录总结

java初探之登录补充

初探JSP与LEeclipse

Java学习笔记之十三初探Java面向对象的过程及代码实现

java初探之秒杀的安全

java初探之缓存技术