原文地址
前言
总在别人的网站查询Steam
游戏史低总觉得有点没意思,我们来做一个自己的Steam
游戏辅助平台吧。这里我已经实现部分,有需要的小伙伴可以直接跳转参考,Steam游戏史低查询
首先,这里我们不讨论前端页面展示,前端页面按照个人喜好设计就可以。其次,我们这里不使用爬虫抓取数据,毕竟我们不是专门做数据平台的,没有必要浪费服务资源去进行大批量的数据抓取
实现
API
那么我们需要先拿到Steam
平台的Api
:
- 官网公开接口,Developer Community Steam Web API以及Steamworks Web API
- 如果你觉得官网对于
Api
的解释不够详细,比如枚举,那么你可以参考网友总结的,steamwebapi - 当你认为官网的
Api
不能满足你的需求的时候,你可以参考SteamApis所提供的接口支持,这是收费的,你总不能叫给人白给你打工吧 - 如果你和站长一样,是个穷逼的话,可以参考IsThereAnyDeal所提供的游戏平台数据查询接口支持,免费的,暂时
- 当然,最后一个不知道从哪冒出来,但是大伙都知道的
Steam
游戏详情接口http://store.steampowered.com/api/appdetails?appids=
,类似接口可以参考StorefrontAPI,这些接口不稳定可能随时会无法调通 - 如果你有更好的或者更多的
Steam
接口提供网站,欢迎分享在评论区
调用
和之前一样,我们技术栈是Java17
+Spring3
+Cloud4
的架构,所以我们这里使用的是Spring Cloud OpenFeign。openfeign
没啥好说的,但是这里着重提一下,如果你调用的是官网接口,那么其实没有特殊处理下你是无法直接获得相关数据的,我们需要使用中间服务器转发。关于转发服务器构建,请参考本站的构建自己的中间服务器以及构建自己的中间服务器(续篇),这里简单说下openfeign
的中间服务器配置,如何使用自己的中间服务器转发请求
openfeign的中间服务器配置
我们可以从openfeign
的源码FeignAutoConfiguration中了解到,openfeign
可以使用的client
有OkHttpClient
,ApacheHttp5Client
以及HttpClient
我们这里就直接使用默认的Client
写入代理
@Configuration
public class FeignConfiguration {
@Value("${nezuko.proxy.host}")
private String proxyHost;
@Value("${nezuko.proxy.port}")
private Integer proxyPort;
@Bean
public Client feignClient() {
return new Client.Proxied(null, null,
new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort)));
}
}
当然,你也可以使用其他的客户端,比如okHttp
,当然需要在配置文件中将spring.cloud.openfeign.okhttp.enabled
设置为true
@Configuration
public class FeignConfiguration {
@Value("${nezuko.proxy.host}")
private String proxyHost;
@Value("${nezuko.proxy.port}")
private Integer proxyPort;
@Bean
public OkHttpClient okHttpClient() {
return new OkHttpClient.Builder()
.proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort)))
.build();
}
}
所以我们无论使用自己的代理服务还是其他方式,都只需要一个地址和一个端口即可。地址好办,如果没有使用容器那么地址就是localhost
,如果使用了容器那么地址就是宿主机地址,所以我们主要拿到端口就可以了
端口获取
此处相关内容参考原文,csdn相关规定不允许发
举例
比如我们我们这里实现一个,查询游戏列表,然后展示详情和史低的功能,我们的实现如下
控制层
@RestController
@RequestMapping("/steam")
public class StreamController {
@Autowired
public StreamController(ISteamService steamService) {
this.steamService = steamService;
}
private final ISteamService steamService;
@GetMapping("/game/{steamId}/detail")
public ResultObj<SteamGameDetailVo> getGameDetail(@PathVariable String steamId) {
return ResultObj.success(ConversionUtils.convertObj(
steamService.getGameDetail(steamId), SteamGameDetailVo.class));
}
@GetMapping("/game/search")
public ResultObj<List<SteamGameSimpleVo>> searchGame(@RequestParam String name) {
return ResultObj.success(ConversionUtils.convertList(
steamService.getGameList(name), SteamGameSimpleVo.class));
}
}
实现层
@Service
@Slf4j
public class SteamServiceImpl implements ISteamService {
@Resource
private SteamStoreApiService steamStoreApiService;
@Resource
private AnyDealApiService anyDealApiService;
@Override
public SteamGameDetailBo getGameDetail(String steamId) {
//param
SteamStoreAppDetailParam param = new SteamStoreAppDetailParam();
param.setAppIdList(Collections.singletonList(steamId));
//call
Map<String, SteamStoreAppDetailVo> steamIdDetailMap = steamStoreApiService.getAppDetail(param);
//check
if (CollectionUtils.isEmpty(steamIdDetailMap) ||
ObjectUtils.isEmpty(steamIdDetailMap.get(steamId)) ||
!steamIdDetailMap.get(steamId).getSuccess()) {
//todo
throw new RuntimeException();
}
//return
return ConversionUtils.convertObj(steamIdDetailMap.get(steamId).getData(), SteamGameDetailBo.class);
}
@Override
public List<SteamGameSimpleBo> getGameList(String name) {
//param
AnyDealSearchGameParam param = new AnyDealSearchGameParam();
param.setName(name);
//call
AnyDealSearchVo anyDealSearchVo = anyDealApiService.searchGame(param);
//check
if (ObjectUtils.isEmpty(anyDealSearchVo) ||
ObjectUtils.isEmpty(anyDealSearchVo.getData()) ||
ObjectUtils.isEmpty(anyDealSearchVo.getData().getDealGameSimpleBoList())) {
return new ArrayList<>();
}
//render
List<SteamGameSimpleBo> results = ConversionUtils.convertList(
anyDealSearchVo.getData().getDealGameSimpleBoList(), SteamGameSimpleBo.class);
List<String> dealPlainList = results.stream().map(SteamGameSimpleBo::getDealPlain).toList();
AnyDealGetOverviewVo gameInfo =
anyDealApiService.getGameOverview(new AnyDealGetOverviewParam(dealPlainList));
if (!ObjectUtils.isEmpty(gameInfo) && !ObjectUtils.isEmpty(gameInfo.getData())) {
for (SteamGameSimpleBo simpleBo : results) {
overviewRender(gameInfo, simpleBo);
}
}
results.removeIf(result -> null == result.getSteamId());
//return
return results;
}
private void overviewRender(AnyDealGetOverviewVo gameInfo, SteamGameSimpleBo simpleBo) {
try {
AnyDealGameOverviewBo overviewBo = gameInfo.getData().get(simpleBo.getDealPlain());
Matcher matcher = Pattern.compile("/[0-9]+/").matcher(overviewBo.getPrice().getUrl());
String steamId = null;
while (matcher.find()) {
steamId = matcher.group();
}
assert steamId != null;
steamId = steamId.replace("/", "");
simpleBo.setSteamId(Long.valueOf(steamId));
simpleBo.setImageUrl(String.format(SteamConstant.GUESS_STEAM_IMAGE, steamId));
simpleBo.setCurPrice(overviewBo.getPrice().getPrice());
simpleBo.setLowestPrice(overviewBo.getLowest().getPrice());
} catch (NullPointerException ignore) {
}
}
}
openfeign
调用接口
@FeignClient(name = "steam-store-api", url = "${nezuko.steam.api.steam-store}")
public interface SteamStoreApiService {
@GetMapping("/api/appdetails")
Map<String, SteamStoreAppDetailVo> getAppDetail(@SpringQueryMap SteamStoreAppDetailParam param);
}
@FeignClient(name = "any-deal-api", url = "${nezuko.steam.api.any-deal}")
public interface AnyDealApiService {
@GetMapping("/v02/search/search")
AnyDealSearchVo searchGame(@SpringQueryMap AnyDealSearchGameParam param);
@GetMapping("/v01/game/overview")
AnyDealGetOverviewVo getGameOverview(@SpringQueryMap AnyDealGetOverviewParam param);
}
参数对象
@Data
public class SteamStoreAppDetailParam {
@Param("appids")
private List<String> appIdList;
/**
* <a href="https://partner.steamgames.com/doc/store/localization/languages">steam language</a>
*/
@Param("l")
private String language = SteamConstant.DEFAULT_LANG_CODE;
@Param("cc")
private String countryCode;
@Param("filters")
private List<String> fileterList;
}
@Data
public class AnyDealSearchGameParam {
private String key = SpringNezukoConfiguration.StaticConfig.anyDealKey;
@Param("q")
private String name;
private Integer limit = 20;
@Param("strict")
private Integer isStrict = 0;
}
@Data
@NoArgsConstructor
public class AnyDealGetOverviewParam {
private String key = SpringNezukoConfiguration.StaticConfig.anyDealKey;
private String region = SteamConstant.DEFAULT_REGION;
private String country = SteamConstant.DEFAULT_COUNTRY;
private String shop = SteamConstant.DEFAULT_SHOPS;
private String allowed = SteamConstant.DEFAULT_SHOPS;
private String plains;
/**
* 该接口不支持List<String>的url写法,只支持逗号分隔的写法
*/
public AnyDealGetOverviewParam(List<String> dealPlainList) {
plains = String.join(",", dealPlainList);
}
}
至此我们就可以通过查询列出列表
然后点击列表查看详情了
最后
关于最后的例子只是一个简单实现,没有使用很多字段,小伙伴们可以在看下API
后写出一个内容丰富的,带有自己色彩的页面