美文网首页单车
单车第四天

单车第四天

作者: shenyoujian | 来源:发表于2018-09-25 15:14 被阅读23次

    转自http://coder520.com/
    1、跟移动端约定好传输的数据格式:json
    1.1、写一个工具类来装我们要返回给移动端的信息
    该类有三个属性:状态码code默认200,返回的信息message(如出现异常返回错误信息),返回请求完后返回的数据(使用泛型)
    状态吗直接写200不好魔鬼数字,需要定义一个常量类Constants来保存常用的状态码

    public class Constants {
        /**自定义状态码 start**/
        public static final int RESP_STATUS_OK = 200;
        
        public static final int RESP_STATUS_NOAUTH = 401;
        
        public static final int RESP_STATUS_INTERNAL_ERROR = 500;
        
        public static final int RESP_STATUS_BADREQUEST = 400;
        /**状态码 end**/
    
    }
    
    @Data
    public class ApiResult<T> {
    
        private int code = Constants.RESP_STATUS_OK;
        private String message;
        private T data;
    }
    

    2、实现登录的controller方法,从之前可以知道当用户成功登录后,我们需要返回一个token(类似session),里面含有包含用户的各种信息,但是在返回之前需要接受一个json格式的数据(手机号和验证码还有一个对称加密的key,登录传递过来的数据),然后再通过注解responsebody转换为对象。所以先写一个接受数据的类

    @Data
    public class LoginInfo {
    
        /**登录信息密文**/
        private String data;
    
        /**RSA加密的AES的密钥**/
        private String key;
    }
    
    

    api实现,拿到数据之后进行校验,这里校验失败我们不能抛出exception,这是业务逻辑错误,不是系统错误,我们的程序并没有崩溃,所以需要我们自定义我们的exception,如下,校验成功之后调用业务逻辑层的方法并传入参数。

     @RequestMapping(value = "/login", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
        public ApiResult<String> login(@RequestBody LoginInfo loginInfo) {
    
            ApiResult<String> resp = new ApiResult();
    
            try {
    
                //进行校验
                String data = loginInfo.getData();
                String key = loginInfo.getKey();
                if (StringUtils.isBlank(data) || StringUtils.isBlank(key)) {
                    throw new MaMaBikeException("校验失败!");
                }
                // 登录成功,返回token
                String token = userService.login(data, key);
                resp.setData(token);
    
            } catch (MaMaBikeException e) {
                //校验失败
                log.error(e.getMessage());
                resp.setCode(Constants.RESP_STATUS_INTERNAL_ERROR);
                resp.setMessage(e.getMessage());
            } catch (Exception e) {
                // 登录失败,返回失败信息,就不用返回data
                // 记录日志
                log.error("Fail to login", e);
                resp.setCode(Constants.RESP_STATUS_INTERNAL_ERROR);
                resp.setMessage("内部错误!");
            }
            return resp;
        }
    
    public class MaMaBikeException extends Exception {
    
        public MaMaBikeException(String message){
            super(message);
        }
    
        public int getStatusCode(){
            return Constants.RESP_STATUS_INTERNAL_ERROR;
        }
    }
    

    业务逻辑方法,先进行解密之后校验,然后使用fastjson去转换前端传过来的json数据。

       /**
         * Author ljs
         * Description 登录业务
         * Date 2018/9/3 23:01
         **/
        @Override
        public String login(String data, String key) throws MaMaBikeException {
            String decryptData = null;
            String token = null;
            try {
    
                //RSA解密AES的key
                byte[] aesKey = RSAUtil.decryptByPrivateKey(Base64Util.decode(key));
                //AES的key解密AES加密数据
                decryptData = AESUtil.decrypt(data, new String(aesKey, "utf-8"));
                if (decryptData == null) {
                    throw new Exception();
                }
                //解密成功后,使用fastjson转为对象,因为移动端传过来的是json数据
                JSONObject jsonObject = JSON.parseObject(decryptData);
                String mobile = jsonObject.getString("mobile"); //电话
                String code = jsonObject.getString("code"); //验证码
                String platform = jsonObject.getString("platform"); //机器类型
                //String channelId = jsonObject.getString("channelId"); //推送频道编码, 单个设备唯一
    
                //转换为json对象获取值后进行校验
                if(StringUtils.isBlank(mobile)|| StringUtils.isBlank(code)||
                        StringUtils.isBlank(platform)){
                    throw new Exception();
                }
    
    
                //去redis取验证码比较手机号码和验证码是否匹配 若匹配 说明是本人手机
    
                //判断用户是否存在数据库,如果存在,生成token,存入redis,如果不存在,帮他注册,插入数据库
                
    
    
    
            } catch (Exception e) {
                log.error("Fail to decypt data", e);
                //传给移动端
                throw new MaMaBikeException("数据解析错误!");
            }
            return null;
        }
    

    使用postman测试是否解密成功,先模拟移动端加密数据和加密key

     /**AES加密数据,客户端操作开始**/
            String key = "123456789abcdefg";            //约定好的key
            String result = "{'mobile':'18319830032','code':'6666','platform':'android'}";
            //传输的数据
            String enResult = encrypt(result, key);     //加密
            System.out.println(enResult);
            /**RSA加密AES的密钥,客户端操作结束**/
            byte[] enKey = RSAUtil.encryptByPublicKey(key.getBytes(), "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCEPB4Y7bd4ttV3phsm7VpR lmG0j19QUQWRG+MVCgw7f7ahvgwiXpwrqWP4hyZFxlFRUT4PlS11cKNut1Qm xjco1pYIxZUG6TfQj+a9rnUOGogdkyS76IpKi5/xal6MTmPqlfpE9SkBLvDc qLFX8FBo0+/ReoPrIPg3H4Saj99tOwIDAQAB");
            //需要再转码不然在http传输会出问题,因为上面输出乱码
            String baseKey = Base64Util.encode(enKey);
            System.out.println(baseKey);
    
    //加密后的数据
    WLixCdk7c4m13V9lmWG1LEZsQoZSGKAdZvJzzBnTOSi1oJLSj/8RVq55c4d+ ekmkG6ak+zJInfUT5qZMUnlD8w==
    //加密后的key
    PCwEbSCsguOzH7XtGD/dUb09DDoZUROJ1m60JPYXYWBYHA+1HM4aEqjNZsde +u1CURklVsw203kdZihwmb0eI7x1DIWB9KdZhMHK1jAA+rPeAhXUvFxblj8w l39cIgyErSqoK5YOjM71zeKKmEvPxn8xoCfh6WYj9fHouExeycY=
    
    image.png image.png

    ok,成功拿到。

    从jsonobject拿到验证码后,去redis里看看发送之前存的验证码是否与传过来的匹配,为什么要去redis取验证码呢,凡是有过期的东西都可以使用redis的key来使用,因为redis的key有过期事件。
    首先整合redis,不要使用springboot提供的,还是使用jedis。

    pom加入依赖

    <!--整合jedis-->
            <dependency>
                <groupId>redis.clients</groupId>
                <artifactId>jedis</artifactId>
            </dependency>
    

    接着配置端口啊什么的,不要写在基础配置里,因为端口在开发和产品中可能不同,所以这里redis的配置写在dev的配置文件里。空闲连接5,总共连接10,超时时间3秒

    #reids
    redis:
        host: 127.0.0.1
        port: 6379
        auth:
        max-idle: 5
        max-total: 10
        max-wait-millis: 3000
    

    写个工具包,专门操作redis,两个类一个redis连接池,一个redis操作。然后初始化连接池需要去读取dev配置文件,如果很多类需要去读取配置文件里的值,到处注入,不好,我们可以干脆创建一个参数类,里面存放配置文件里的@Value值,然后以后修改也在这个类里修改就行。

    @Data
    @Component
    public class Parameters {
    
        /*****redis config start*******/
        @Value("${redis.host}")
        private String redisHost;
        @Value("${redis.port}")
        private int redisPort;
        @Value("${redis.auth}")
        private String redisAuth;
        @Value("${redis.max-idle}")
        private int redisMaxTotal;
        @Value("${redis.max-total}")
        private int redisMaxIdle;
        @Value("${redis.max-wait-millis}")
        private int redisMaxWaitMillis;
        /*****redis config end*******/
    }
    

    现在只需要注入parameters就行,redis包装类三段,初始化连接池,添加配置参数,返回这个连接池实例,但是还是有一个问题,当实例化一个连接池的时候,怎么确保init方法一定会执行,可以使用spring的一个注解postConstruct,这个注解好比静态代码块,这样就不用再getjedispool方法里调用init方法了。

    @Component
    @Slf4j
    public class JedisPoolWrapper {
    
        private JedisPool jedisPool = null;
    
        @Autowired
        private Parameters parameters;
    
        @PostConstruct
        public void init() throws MaMaBikeException {
            try{
                JedisPoolConfig config = new JedisPoolConfig();
                config.setMaxTotal(parameters.getRedisMaxTotal());
                config.setMaxIdle(parameters.getRedisMaxIdle());
                config.setMaxWaitMillis(parameters.getRedisMaxWaitMillis());
    
                jedisPool = new JedisPool(config,parameters.getRedisHost(),parameters.getRedisPort(),2000,parameters.getRedisAuth());
            }catch (Exception e){
                log.error("Fail to initialize jedis pool", e);
                throw new MaMaBikeException("Fail to initialize jedis pool");
            }
        }
    
        public JedisPool getJedisPool(){
            return jedisPool;
        }
    
    
    }
    
     @Autowired
        private JedisPoolWrapper jedisPoolWrapper;
    
        /**
         * 缓存 可以value 永久
         *
         * @param key
         * @param value
         */
        public void cache(String key, String value) {
            try {
                JedisPool pool = jedisPoolWrapper.getJedisPool();
                if (pool != null) {
                    try (Jedis Jedis = pool.getResource()) {
                        Jedis.select(0);        //选择redis第0片区
                        Jedis.set(key, value);
                    }
                }
            } catch (Exception e) {
                log.error("Fail to cache value", e);
            }
        }
    
        /**
         * 获取缓存key
         *
         * @param key
         * @return
         */
        public String getCacheValue(String key) {
            String value = null;
            try {
                JedisPool pool = jedisPoolWrapper.getJedisPool();
                if (pool != null) {
                    try (Jedis Jedis = pool.getResource()) {
                        Jedis.select(0);
                        value = Jedis.get(key);
                    }
                }
            } catch (Exception e) {
                log.error("Fail to get cached value", e);
            }
            return value;
        }
    
        /**
         * 设置key value 以及过期时间
         *
         * @param key
         * @param value
         * @param expiry
         * @return
         */
        public long cacheNxExpire(String key, String value, int expiry) {
            long result = 0;
            try {
                JedisPool pool = jedisPoolWrapper.getJedisPool();
                if (pool != null) {
                    try (Jedis jedis = pool.getResource()) {
                        jedis.select(0);
                        result = jedis.setnx(key, value);
                        jedis.expire(key, expiry);
                    }
                }
            } catch (Exception e) {
                log.error("Fail to cacheNx value", e);
            }
    
            return result;
        }
    
        /**
         * 删除缓存key
         *
         * @param key
         */
        public void delKey(String key) {
            JedisPool pool = jedisPoolWrapper.getJedisPool();
            if (pool != null) {
    
                try (Jedis jedis = pool.getResource()) {
                    jedis.select(0);
                    try {
                        jedis.del(key);
                    } catch (Exception e) {
                        log.error("Fail to remove key from redis", e);
                    }
                }
            }
        }
    
    

    整合好后,继续写业务逻辑
    去redis取验证码比较手机号码和验证码是否匹配 若匹配 说明是本人手机,用户不存在,帮他注册

    @Autowired
    private CommonCacheUtil cacheUtil;
    
     //去redis取验证码比较手机号码和验证码是否匹配 若匹配 说明是本人手机
                String verCode = cacheUtil.getCacheValue(mobile);
                User user = null;
                ///用code去匹配verCode,因为code上面已经验证过是不为null,而v可能为null,null.equals空指针异常
                if (code.equals(verCode)) {
                    //手机匹配
                    user = userMapper.selectByMobile(mobile);
                    if(user==null){
                        //用户不存在,帮他注册
                        user = new User();
                        user.setMobile(mobile);
                        user.setNickname(mobile);       //默认是手机号
                        userMapper.insertSelective(user);
                    }
    
                }else {
                    throw new MaMaBikeException("验证码或者手机号不匹配");
                }
    
    
    <select id="selectByMobile" resultMap="BaseResultMap" parameterType="java.lang.String" >
        select
        <include refid="Base_Column_List" />
        from user
        where mobile = #{mobile}
      </select>
    

    生成token

    //生成token
                try{
                    token = this.generateToken(user);
    
                }catch (Exception e){
                    throw new MaMaBikeException("fail.to.generate.token");
                }
    

    使用用户id和iphone和系统当前时间然后md5加密生成token

     /**
         * Author ljs
         * Description 生成唯一标识token,并且把token加密
         * Date 2018/9/4 15:04
         **/
        private String generateToken(User user)
                throws Exception {
            String source = user.getId() + ":" + user.getMobile() + System.currentTimeMillis();
            return MD5Util.getMD5(source);
        }
    

    在存入redis之前,token作为key,value是用户的信息,但是我们原本的user实体类里的属性不太够,所以创建一个新的实体类userElement,而且我们value是一个对象,使用的往redis存map,所以需要两个方法,map转对象,对象转map

    /**
     * Author ljs
     * Description 用于缓存的user信息体
     * Date 2018/9/4 15:09
     **/
    @Data
    public class UserElement {
    
        private long userId;
    
        private String mobile;
    
        private String token;
    
        private String platform;  //ios或者andriod
    
        private String pushUserId;  //单设备推送标识
    
        private String pushChannelId;   //所以设备推送标识
    
    
        /**
         * 转 map
         * @return
         */
        public Map<String, String> toMap() {
            Map<String, String> map = new HashMap<String, String>();
            map.put("platform", this.platform);
            map.put("userId", this.userId + "");
            map.put("token", token);
            map.put("mobile", mobile);
            if (this.pushUserId != null) {
                map.put("pushUserId", this.pushUserId);
            }
            if (this.pushChannelId != null) {
                map.put("pushChannelId", this.pushChannelId);
            }
            return map;
        }
    
        /**
         * map转对象
         * @param map
         * @return
         */
        public static UserElement fromMap(Map<String, String> map) {
            UserElement ue = new UserElement();
            ue.setPlatform(map.get("platform"));
            ue.setToken(map.get("token"));
            ue.setMobile(map.get("mobile"));
            ue.setUserId(Long.parseLong(map.get("userId")));
            ue.setPushUserId(map.get("pushUserId"));
            ue.setPushChannelId(map.get("pushChannelId"));
            return ue;
        }
    
    }
    

    redis操作方法需要添加一个当登录时往redis存哈希的方法,之前userElement为什么要加入token属性就是为了在这里能取出来然后设置为该哈希的key,而设置userid是为了获取token,先获取token之后再根据token去获取用户。

    /**
         * 登录时设置token
         * @param ue
         */
        public void putTokenWhenLogin(UserElement ue) {
            JedisPool pool = jedisPoolWrapper.getJedisPool();
            if (pool != null) {
    
                try (Jedis jedis = pool.getResource()) {
                    jedis.select(0);
                    Transaction trans = jedis.multi();
                    try {
                        //重新设置token
                        trans.del(TOKEN_PREFIX + ue.getToken());
                        //token为key,用户信息转为map之后为value
                        trans.hmset(TOKEN_PREFIX + ue.getToken(), ue.toMap());
                        //设置超时时间3天
                        trans.expire(TOKEN_PREFIX + ue.getToken(), 2592000);
                        //sadd将多个token存入一个集合key中
                        //因为该用户可能在多个设备登录有多个token,我们需要提醒一下
                        trans.sadd(USER_PREFIX + ue.getUserId(), ue.getToken());
                        trans.exec();
                    } catch (Exception e) {
                        trans.discard();
                        log.error("Fail to cache token to redis", e);
                    }
                }
            }
        }
    
    

    最后存入redis,并且返回给移动端生成的token就行了

     /**存入redis**/
                UserElement ue = new UserElement();
                ue.setMobile(mobile);
                ue.setUserId(user.getId());
                ue.setToken(token);
                ue.setPlatform(platform);
               // ue.setPushChannelId(channelId);
                cacheUtil.putTokenWhenLogin(ue);
    
     return token;
    

    启动服务器验证,先往redis存一个18319830032,6666用于验证
    然后发送


    image.png

    这里发送请求的时候报了一个错,百度了说请求用http,我也是用了http,后面再解决吧,这里主要就是实现生成token,存入redis,报错没影响就算了。

     Note: further occurrences of HTTP header parsing errors will be logged at DEBUG level.
    java.lang.IllegalArgumentException: Invalid character found in method name. HTTP method names must be tokens
    

    ok返回成功并且也确实存入到redis里。


    image.png
    image.png

    相关文章

      网友评论

        本文标题:单车第四天

        本文链接:https://www.haomeiwen.com/subject/ftxwwftx.html