springboot+jpa+redis+quzartz+elasticsearch實現微信論壇小程序(三)

Redis的使用

pom

需要加入cache包及redis

        <!--緩存-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
        <!--redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <version>2.1.3.RELEASE</version>
        </dependency>

yml

配置redis的基本參數

  ##redis配置澄阳,默認密碼為空
  redis:
    host: localhost
    # Redis服務器連接端口
    port: 6379
    jedis:
      pool:
        #連接池最大連接數(使用負值表示沒有限制)
        max-active: 100
        # 連接池中的最小空閑連接
        max-idle: 10
        # 連接池最大阻塞等待時間(使用負值表示沒有限制)
        max-wait: 100000
    # 連接超時時間(毫秒)
    timeout: 5000
    #默認是索引為0的數據庫
    database: 0

redis配置類

需要配置能夠使用@Cacheable@CacheEvict注解來進行緩存,同時也需要使用redisTemplate進行一些操作玲躯,所以要配置CacheManagerRedisTemplate模聋,Springboot使用的序列化方式為jdkSerializable,存儲的是二進制字節(jié)碼料睛,不易讀并且很長丐箩,于是使用Jackson2JsonRedisSerializer做為值的序列化方法,鍵的序列化方法還是使用String恤煞。

@Configuration
@EnableCaching
@Slf4j
public class RedisConfig {

    private static final Duration timeToLive = Duration.ofDays(1);

    /**
     * 采用RedisCacheManager作為緩存管理器
     * @param connectionFactory
     */
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
        // 1.設置redis緩存配置
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(timeToLive) //設置過期時間
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(keySerializer())) // 設置鍵的序列化方式
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(valueSerializer())) // 設置值得序列化方式
                .disableCachingNullValues(); //不緩存空值
        // 2.建立redis緩存管理
        RedisCacheManager redisCacheManager = RedisCacheManager.builder(connectionFactory)
                .cacheDefaults(redisCacheConfiguration)
                .transactionAware()
                .build();
        log.info("自定義RedisCacheManager加載完成");
        return redisCacheManager;
    }

    /**
     * redisTemplate 序列化使用的jdkSerializeable, 存儲二進制字節(jié)碼, 所以自定義序列化類
     * @param redisConnectionFactory
     * @return
     */
    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        // 1.建立redis模板類
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        // 2.設置value的序列化規(guī)則和 key的序列化規(guī)則
        redisTemplate.setValueSerializer(valueSerializer());
        redisTemplate.setKeySerializer(keySerializer());
        redisTemplate.setHashKeySerializer(keySerializer());
        redisTemplate.setHashValueSerializer(keySerializer());
        redisTemplate.afterPropertiesSet();
        log.info("自定義RedisTemplate加載完成");
        return redisTemplate;
    }


    /**
     * 鍵的序列化方法
     * @return
     */
    private RedisSerializer<String> keySerializer() {
        return new StringRedisSerializer();
    }

    /**
     * 值的序列化方法
     * @return
     */
    private RedisSerializer<Object> valueSerializer() {
        // 1.創(chuàng)建 序列化類
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        // 2.設置可見度
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        // 3.啟動默認的類型
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        // 4.序列化類屎勘,對象映射設置
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

        return jackson2JsonRedisSerializer;
    }
}

實體類到map的互換

為了可以直接將實體存到redishash類型,寫一個實體與map轉換的工具類居扒,需要引入beanutils的依賴:

<dependency>
    <groupId>commons-beanutils</groupId>
    <artifactId>commons-beanutils</artifactId>
    <version>1.9.3</version>
</dependency>
public class EntityUtils {

    public static Map<String, String> objectToHash(Object obj) {
        try {
            Map<String, String> map = new HashMap();
            BeanInfo beanInfo = Introspector.getBeanInfo(obj.getClass());
            PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
            for (PropertyDescriptor property : propertyDescriptors) {
                if (!property.getName().equals("class")) {
                    if (property.getReadMethod().invoke(obj) != null) {
                        // 時間類型會錯亂所以吧時間手動轉換成long;
                        if (property.getReadMethod().invoke(obj) != null) {
                            if ("java.util.Date".equals(property.getPropertyType().getTypeName())) {
                                Date invoke = (Date) property.getReadMethod().invoke(obj);
                                long time = invoke.getTime();
                                map.put(property.getName(), String.valueOf(time));
                            } else {
                                map.put(property.getName(), "" + property.getReadMethod().invoke(obj));
                            }
                        }
                    }
                }
            }
            return map;
        } catch (InvocationTargetException | IllegalAccessException | IntrospectionException e) {
            throw new RuntimeException(e);
        }
    }

    public static <T> T hashToObject(Map<?, ?> map, Class t) {
        // 轉換注冊器
        ConvertUtils.register(new LongConverter(null), Long.class);
        ConvertUtils.register(new ByteConverter(null), Byte.class);
        ConvertUtils.register(new IntegerConverter(null), Integer.class);
        ConvertUtils.register(new DoubleConverter(null), Double.class);
        ConvertUtils.register(new ShortConverter(null), Short.class);
        ConvertUtils.register(new FloatConverter(null), Float.class);
        ConvertUtils.register(new Converter() {
            public Object convert(Class type, Object value) {
                if (value == null) {
                    return null;
                }
                return new Date(Long.valueOf((String) value));
            }
        }, Date.class);

        try {
            Object o = t.newInstance();
            BeanUtils.populate(o, (Map) map);
            return (T) o;

        } catch (IllegalAccessException | InvocationTargetException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
        return null;
    }
}

這樣就可以在項目中使用redis概漱,并且可以方便的把實體類直接存入redishash類型中了。

微信小程序

pom

        <!--微信小程序SDK-->
        <dependency>
            <groupId>com.github.binarywang</groupId>
            <artifactId>weixin-java-miniapp</artifactId>
            <version>3.3.0</version>
        </dependency>

yml

wechat:
  appId: 小程序appid
  appSecret: 小程序secret

小程序賬戶配置

@Component
@Data
@ConfigurationProperties(prefix = "wechat")
public class WeChatAccountCogfig {

    /**
     * 公眾平臺id
     */
    private String appId;

    /**
     * 公眾平臺secret
     */
    private String appSecret;
}

小程序配置類

@Configuration
public class WeChatMaConfig {

    @Autowired
    WeChatAccountCogfig weChatAccountCogfig;

    @Bean
    public WxMaService wxMaService(){
        WxMaService wxMaService = new WxMaServiceImpl();
        wxMaService.setWxMaConfig(wxMaConfigStorage());
        return wxMaService;
    }

    @Bean
    public WxMaInMemoryConfig wxMaConfigStorage(){
        WxMaInMemoryConfig wxMaInMemoryConfig = new WxMaInMemoryConfig();
        wxMaInMemoryConfig.setAppid(weChatAccountCogfig.getAppId());
        wxMaInMemoryConfig.setSecret(weChatAccountCogfig.getAppSecret());
        return wxMaInMemoryConfig;
    }
}

小程序配置也完成了喜喂,我們看一下微信小程序登錄流程瓤摧。

登錄流程

微信小程序文檔

微信小程序登錄流程

說明:

  1. 調用 wx.login() 獲取 臨時登錄憑證code ,并回傳到開發(fā)者服務器玉吁。
  2. 調用 auth.code2Session 接口照弥,換取 用戶唯一標識 OpenID會話密鑰 session_key

之后開發(fā)者服務器可以根據用戶標識來生成自定義登錄態(tài)进副,用于后續(xù)業(yè)務邏輯中前后端交互時識別用戶身份这揣。

注意:

  1. 會話密鑰 session_key 是對用戶數據進行 加密簽名 的密鑰。為了應用自身的數據安全影斑,開發(fā)者服務器不應該把會話密鑰下發(fā)到小程序给赞,也不應該對外提供這個密鑰
  2. 臨時登錄憑證 code 只能使用一次
    了解了基本的登錄流程矫户,我們就可以開始寫小程序的登錄模塊了片迅。

小程序登錄Controller

主要分為兩個部分,登錄模塊和獲取用戶信息模塊皆辽。對登錄模塊柑蛇,選擇redis進行3rd_sessionId的存儲,3rd_sessionId的生成使用sessionKey+openId+currentTimeMillis()進行MD5加密后的結果驱闷,當重復登錄時需要使之前的3rd_sessionId失效耻台,所以需要使用鍵sessionId保存session信息,用鍵openId保存sessionId信息遗嗽,當發(fā)現有當前用戶的3rd_sessionId還沒失效粘我,又進行的登錄,則刪除舊的3rd_sessionId,將3rd_sessionId有效期設置為30天征字。
獲取sessionKey及解密獲得用戶信息都是使用了微信小程序SDK都弹,很好用的SDK,還有配套的demo匙姜,推薦給大家畅厢。

@RestController
@Slf4j
@RequestMapping("/wechat")
public class WeChatController {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Autowired
    private WxMaService wxMaService;

    @Autowired
    private UserServiceImpl userService;

    @GetMapping("/login")
    public ResultVO WeChatLogin(@RequestParam String code){
        String sessionId;
        try{
            // 0.如果code為空,返回錯誤信息
            if (code==null||code.isEmpty()){
                return ResultVOUtil.error(ResultEnum.CODE_ERROR.getCode(), ResultEnum.CODE_ERROR.getMessage());
            }
            // 1.向微信服務器獲取openid和sessionKey
            WxMaJscode2SessionResult session = wxMaService.getUserService().getSessionInfo(code);
            if (session==null){
                return ResultVOUtil.error(ResultEnum.SESSION_ERROR.getCode(), ResultEnum.SESSION_ERROR.getMessage());
            }
            String sessionKey = session.getSessionKey();
            String openId = session.getOpenid();
            // 3.根據openid查詢用戶是否存在
            User user = userService.findUser(session.getOpenid());
            // 4.若用戶不存在則創(chuàng)建用戶
            if (user == null){
                userService.createUser(session.getOpenid());
            }
            // 5.查看redis中是否有登錄信息
            if (redisTemplate.hasKey("openId::" + openId)){
                redisTemplate.delete(redisTemplate.opsForValue().get("openId::" + openId));
            }
            // 6.生成加密的sessionId;
            sessionId = KeyUtil.getSessionId(sessionKey+openId+System.currentTimeMillis());
            // 7.存入redis中
            redisTemplate.opsForValue().set("sessionId::" + sessionId, session,30, TimeUnit.DAYS);
            redisTemplate.opsForValue().set("openId::" + openId, "sessionId::" + sessionId, 30, TimeUnit.DAYS);
        }catch (WxErrorException e){
            log.error(e.getMessage(), e);
            return ResultVOUtil.error(e.getError().getErrorCode(), e.getError().getErrorMsg());
        }
        // 8.返回第三方sessionId交由客戶端保存
        HashMap<String, String> map = new HashMap();
        map.put("sessionId", sessionId);
        return ResultVOUtil.success(map);
    }

    @PostMapping("/info")
    public ResultVO<String> WeChatInfo(@RequestParam String sessionId,
                                       @RequestBody UserInfoForm userInfoForm){
        String rawData = userInfoForm.getRawData();
        String signature = userInfoForm.getSignature();
        String encryptedData = userInfoForm.getEncryptedData();
        String iv = userInfoForm.getIv();
        // 1.查看是否有sessionId信息
        if (!redisTemplate.hasKey("sessionId::" + sessionId)){
            return ResultVOUtil.error(ResultEnum.SESSION_ID_NULL.getCode(), ResultEnum.SESSION_ID_NULL.getMessage());
        }
        // 2.從sessionId中取出sessionKey
        WxMaJscode2SessionResult session = (WxMaJscode2SessionResult) redisTemplate.opsForValue().get("sessionId::" + sessionId);
        String sessionKey = session.getSessionKey();
        // 3.校驗用戶信息
        if (!wxMaService.getUserService().checkUserInfo(sessionKey, rawData, signature)) {
            return ResultVOUtil.error(ResultEnum.USER_INFO_ERROR.getCode(), ResultEnum.USER_INFO_ERROR.getMessage());
        }
        // 4.解密用戶信息
        WxMaUserInfo userInfo = wxMaService.getUserService().getUserInfo(sessionKey, encryptedData, iv);
        // 5.更新用戶信息
        User user = userService.updateUser(userInfo);
        return ResultVOUtil.success(user);
    }
}

至此氮昧,小程序登錄的模塊已經完成框杜,下一章會介紹主要的業(yè)務模塊,包括帖子袖肥、評論及上傳圖片到七牛云對象存儲咪辱。
上一篇:springboot+jpa+redis+quzartz+elasticsearch實現微信論壇小程序(二)
下一篇:springboot+jpa+redis+quzartz+elasticsearch實現微信論壇小程序(四)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市椎组,隨后出現的幾起案子油狂,更是在濱河造成了極大的恐慌,老刑警劉巖寸癌,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件专筷,死亡現場離奇詭異,居然都是意外死亡蒸苇,警方通過查閱死者的電腦和手機磷蛹,發(fā)現死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來溪烤,“玉大人味咳,你說我怎么就攤上這事》帐玻” “怎么了莺葫?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵匪凉,是天一觀的道長枪眉。 經常有香客問我,道長再层,這世上最難降的妖魔是什么贸铜? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮聂受,結果婚禮上蒿秦,老公的妹妹穿的比我還像新娘。我一直安慰自己蛋济,他們只是感情好棍鳖,可當我...
    茶點故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般渡处。 火紅的嫁衣襯著肌膚如雪镜悉。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天医瘫,我揣著相機與錄音侣肄,去河邊找鬼。 笑死醇份,一個胖子當著我的面吹牛稼锅,可吹牛的內容都是我干的。 我是一名探鬼主播僚纷,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼矩距,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了怖竭?” 一聲冷哼從身側響起剩晴,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎侵状,沒想到半個月后赞弥,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡趣兄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年绽左,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片艇潭。...
    茶點故事閱讀 38,100評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡拼窥,死狀恐怖,靈堂內的尸體忽然破棺而出蹋凝,到底是詐尸還是另有隱情鲁纠,我是刑警寧澤,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布鳍寂,位于F島的核電站改含,受9級特大地震影響,放射性物質發(fā)生泄漏迄汛。R本人自食惡果不足惜捍壤,卻給世界環(huán)境...
    茶點故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望鞍爱。 院中可真熱鬧鹃觉,春花似錦、人聲如沸睹逃。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至疗隶,卻和暖如春躬柬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背抽减。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工允青, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人卵沉。 一個月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓颠锉,卻偏偏與公主長得像,于是被迫代替她去往敵國和親史汗。 傳聞我的和親對象是個殘疾皇子琼掠,可洞房花燭夜當晚...
    茶點故事閱讀 42,834評論 2 345

推薦閱讀更多精彩內容