redis+spring 注解Cacheable 设定redis的生存周期。

业务场景:

1、要取得当日汇率(美元兑人民币,及人民币兑美元),精度不高,频率不高,一天取一到两次即可。

2、取得的汇率作为所有用户的基础汇率用做其它运算。所有用户共用一套汇率,不区分用户。

解决方案:

1、初步考虑

     i 、建表,存汇率值。

     ii、用定时任务 调用其它网站提供的汇率API 来更新表中固定汇率。频率用定时任务的参数来设置。

     iii、redis取得表里的值。设置过期时间。

2、后期优化后考虑

    i、因为频率不高,可以直接用httpUrlConnection来爬取。代替了别人提供的API接口,(找到的免费汇率API不太满足方案,要么只能免费一个月,要么汇率只有CNF,没有CNY,要么就是美元换成欧元),方案可以多设置几个网站。以防反爬或其它问题取值失败。

    ii、表也不用建了、定时任务也没必要,直接用@cacheable设定spring缓存时间。过期(一天)后,当第一个用户调用时再爬一次,并缓存上。对用户来说基本上无感。

优缺点:

优的是资源少,速度快,简单易懂。

缺点一个是只适用于精度要求不高的场合,另一个是爬取的通病,爬的对象保不准什么时候就失效,补救措施要多做准备,在这里用了两套待爬网站+实在取不到取固定汇率预定值的方案。

一、配置很简单。


    
        
    

    
        
            
        
        
            
        
    
    
    
    
        
        
        
            
                
                    
                        exchangeRate
                    
                    86400
                
            
        
     

注意红色部分是必须加的

exchangeRate是你要缓存的Cacheable的value

86400是过期时间(一天)。

二、JAVA中注解的使用。

1 controller中因为包含读缓存和爬取的分支,不要写cacheable标签,但要判断是爬还是读缓存。

    /**
     * 汇率取得
     */
    @ApiResponses(value = { @ApiResponse(code = 200, message = "请求成功") })
    @ApiOperation(value = "汇率取得", notes = "汇率取得")
    @ResponseBody
    @RequestMapping(value = "getExchangeRate", method = RequestMethod.POST)
    public InfoResult> getExchangeRate(HttpServletRequest request) throws ParseException {
        /////////////////////////////////////上略
        // 为空重查,不走cache,清除空cache
        Map res = erService.getExchangeRate("fixedValue");
        if (res == null) {
            try {
                res = erService.callExchangeRate();
                erService.clearCache(userId);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        ////////////////////////////////////下略
    }

实现类

@Service
public class esServiceImpl implements esService {
    private static Logger logger = LoggerFactory.getLogger(esImpl.class);
    @Resource(name = "redisTemplate")
    private RedisTemplate> redisTemplate;
	/**
	 * 汇率取得工具
         * 爬取页面信息中的汇率
         * @url https://xxx.com
	 * @return result
	 */
	@Override
        @Cacheable(value="exchangeRate")
	public Map getExchangeRate(String userId) {
        logger.info("接口getExchangeRate从页面取得汇率开始...");
        // 默认值
        Map result = null;
        try {
            // 取得汇率
            result = callExchangeRate();
        } catch (Exception e) {
            e.printStackTrace();
            logger.info("汇率取得失败...");
        }
        logger.info("接口getExchangeRate从页面取得汇率结束...");
		return result;
	}

    /**
     * 取得汇率 目前只取USD转CNY的汇率
     *    方案1 获取来源:从xxx网站取,
     *    方案2 获取来源:从yyy网站取,
     *    注:如果方案1与2都未取到。则取前端的默认值。
     * @return STRING
     */
    public Map callExchangeRate() throws Exception {
        // get api result
        URL url = new URL("https://xxx");
        StringBuilder sbf = new StringBuilder();
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        InputStream is = conn.getInputStream();
        BufferedReader reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
        String strRead = null;
        logger.info("方案1: 从xxx取得汇率。");
        while ((strRead = reader.readLine()) != null) {
            if (strRead.contains("currency_converter_result")) {
                String strD[] = strRead.split("xxx");
                //字符串解析
            }
        }
        // TYPE 2 GET FROM yyy
        if (sbf.length() == 0) {
            logger.info("方案1 失败,采用方案2: 从yyy取得汇率。");
            url = new URL("https://yyy");
            conn = (HttpURLConnection) url.openConnection();
            is = conn.getInputStream();
            reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
// 字符串解析
        String usd2cnyStr = sbf.toString();
        String cny2usdStr = "";
        if (StringUtils.isNotBlank(usd2cnyStr)) {
            double value = Double.valueOf(usd2cnyStr);
            BigDecimal bd = new BigDecimal(1 /value);
            bd = bd.setScale(4,BigDecimal.ROUND_HALF_UP);
            cny2usdStr = String.valueOf(bd);
        }
        is.close();
        reader.close();
        conn.disconnect();
        logger.info("页面取得汇率(1美元兑人民币):"+usd2cnyStr);
        logger.info("页面取得汇率(1人民币兑美元):"+cny2usdStr);
        Map redisMap = new HashMap();
        redisMap.put("USD2CNY",usd2cnyStr);
        redisMap.put("CNY2USD",cny2usdStr);
    /**
     * 清除缓存
     * @param userId userId
     */
    @Override
    @CacheEvict(value = "exchangeRate")
    public void clearCache(String userId) {
        logger.info("汇率缓存已清除");
    }

 

@Cacheable(value = "exchangeRate")是缓存该value

 

调用该方法时,会先从spring cache中取得该KEY中有没有值,如果有,直接跳过方法取值。如果没有则调用方法。

@CacheEvict(value = "exchangeRate")是清除该value的值。

此处的小技巧是用1除以美元兑人民币的汇率得到人民币兑美元,省去了再调爬一次URL。

写在后面:

@Cacheable标签有几点要求:

1、不能用在private上。

2、方案必须有参数、且以 方法名+参数值做为VALUE缓存,如果参数不同也不如行。

3、果是用在实现类上,必须用在override方法上,也就是说必须是实现接口的方法,单一个非override的public方法是不可用的。

你可能感兴趣的:(JAVA)