业务场景:
1、要取得当日汇率(美元兑人民币,及人民币兑美元),精度不高,频率不高,一天取一到两次即可。
2、取得的汇率作为所有用户的基础汇率用做其它运算。所有用户共用一套汇率,不区分用户。
解决方案:
1、初步考虑
i 、建表,存汇率值。
ii、用定时任务 调用其它网站提供的汇率API 来更新表中固定汇率。频率用定时任务的参数来设置。
iii、redis取得表里的值。设置过期时间。
2、后期优化后考虑
i、因为频率不高,可以直接用httpUrlConnection来爬取。代替了别人提供的API接口,(找到的免费汇率API不太满足方案,要么只能免费一个月,要么汇率只有CNF,没有CNY,要么就是美元换成欧元),方案可以多设置几个网站。以防反爬或其它问题取值失败。
ii、表也不用建了、定时任务也没必要,直接用@cacheable设定spring缓存时间。过期(一天)后,当第一个用户调用时再爬一次,并缓存上。对用户来说基本上无感。
优缺点:
优的是资源少,速度快,简单易懂。
缺点一个是只适用于精度要求不高的场合,另一个是爬取的通病,爬的对象保不准什么时候就失效,补救措施要多做准备,在这里用了两套待爬网站+实在取不到取固定汇率预定值的方案。
一、配置很简单。
注意红色部分是必须加的
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
实现类
@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方法是不可用的。