前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >手写一个原神祈愿分析工具

手写一个原神祈愿分析工具

作者头像
易兮科技
发布2022-09-08 11:54:04
2.8K0
发布2022-09-08 11:54:04
举报
文章被收录于专栏:CSDN博客专栏CSDN博客专栏

手写一个原神祈愿分析工具

之前一直通过游创工坊来进行祈愿抽卡数据分析,但是广告太多,而且担心auth_key泄露,于是自己花了一天时间动手实现了个数据分析工具,数据永久保存在本地,没有信息泄露风险,话不多说,先放源码链接和运行截图

在这里插入图片描述
在这里插入图片描述

第一步:解析请求数据Api

  • 无论是电脑或是手机,点击历史祈愿记录后,本地都会发起一次API请求到服务器,查询具体数据,通过抓包或者直接断网刷新,很容易拿到这个API接口,如下(此处我删去了auth_key的部分数据,以免信息泄露):
代码语言:javascript
复制
https://webstatic.mihoyo.com/hk4e/event/e20190909gacha/index.html?authkey_ver=1&sign_type=2&auth_appid=webview_gacha&init_type=200&gacha_id=ebbfa80fdbc30f7cdfed84670d87c018950878&timestamp=1653954735&lang=zh-cn&device_type=mobile&ext=%7b%22loc%22%3a%7b%22x%22%3a463.9453430175781%2c%22y%22%3a330.5148620605469%2c%22z%22%3a1488.346435546875%7d%2c%22platform%22%3a%22Android%22%7d&game_version=CNRELAndroid2.7.0_R8029328_S8227893_D8227893&plat_type=android&region=cn_gf01&authkey=YJHBE%2bKkY%2fuKzXQ63s05Z2L%2f%2fn%2bEsN0XCx5dzsotpulgXnUovr6wOnbzJMrboBnD9KIhzOTxNkdOHtEZe9ZwZDDXvV70rTLydeHcDBBnQe6likCy0iiXkuEDfKtgBLb8ghbir%2bCDIy%2fsY0fQ4DYP4Ohht38ld%2fWudZR6Xp%2bbOxuQ249u%2fDCwDS4FudukFnKx7peYbhO1FtpFUn7zM%2fVgCum7vxTbk8vzO7wV53BtDmjEkRdyQz2%2bozzyNc4s9l6mNonFrK9rDGtHb2nLiM%2flRoKNYWDIazj7Fs8zaJSI%2bzo5Da%2fdNJwEpHaCAvVpbeDZ5YMqu8rdOZ3A1%2bpWxECTi9RfnaQXuSh1oi6nz3%2bbL9i4KC%2b%2b254wwbJQxLTWmL272gMtJtm7EZfaF21eXmfNhVbY5E2n9lq7P%2bcGZy%2bE4Wzrj7UEp%2fuQ9322Z7t%2b332kgjvwjAj7BGXt%2fZ2RU7jXtg4yLx4OGo0nIqE%2ftLJf2ZnzKZ75LR01WwUP4yKyOToe0un4LxeE3rVBHU4StrFrfN8C39gsE4lTr%2bDoU%2fP82UFIkeQ9lHXDK4H1%2bzvxldyytNr6eCdA5T5iaREMZ0cmq8VtYMr0zVbpyEXEIfjwTIcXoX9nCrh8KH8IKwb9v7OqaWV1Rr5dmjP%2bqlAyn9j1v7xebWLUJw4on%2bO6MIHlqE2t7mjGD%2b%2fsbNgHvLuMDuCGwXQrOFXNTev%2fgK2K9HgEA5gyaeXaotcYhfv2%2by9hi5On8oBaEtxeKiMIZphmb%2fJsEIiDK%2bQDnK4BujqJyekuX%2bz5JpT8tRbW1k%2bIqcRsDKIEO%2bje4iPM9kmOnS8IEngu4kQphc2Kz8CUjytd3VteQBipB%2bz%2f2494UAdkYHlgmUSdYnfR%2bnIzz9aGOb3OA9TkRKwl3R%2bJrIniF3HQdtOew72%2fVvAQ3yNYGMdYV8mfMvvK8GeSFNmAltD64YwQ%2fl%2foU9HzsTzQWB2DWNBExF5zX%2bNt%2fwfOTunHODQmTrMUTgerCuypev44CQZvISRc9CLNPZqRkcgueJOb2e2k5iGgi1BTe%2fjM6TmKQA6TDnANAqyurWKqBP%2b0TcHelBGCaeTbncpA4YLpbjnvdPHzzn2UjP%2bVg%2bWu1rmyTXscm1CI3w94I7AnAfgaHOWAotusROx%2bR%2b4f9WrVnUDpfTeztLiWzK7O%2fvabOT9y6cmgKFZ%2bN79iKiRM9Fm%2b3EOQ4tXF1MnJ7WqSkjUWRtfSwWBH5eg37cx0065aph4Bh4ZYKMLmwa0sjS6gC1F34Q%3d%3d&game_biz=hk4e_cn
  • 分析拿到关键参数:
    1. authkey_ver:用户身份类型
    2. authkey:用户身份标识(一个authkey对应一个账户)
    3. lang:当前系统语言
    4. timestamp:请求时间戳(超过一定时间会使链接失效)
  • 此处仅能拿到这四个参数,但通过抓包发现,真正获取数据的API是这个:https://hk4e-api.mihoyo.com/event/gacha_info/api/getGachaLog
  • 除以上四个参数外,还需拼接四个参数:
    1. gacha_type:祈愿类型(新手祈愿、常驻池祈愿、活动祈愿、武器祈愿)
    2. page:分页参数,当前页码
    3. size:分页参数,每一页的大小
    4. end_id:查询起始数据编码,此次分页请求会从这个ID开始查询,默认为0时,查询所有数据

第二步:API请求链接转换以及解析数据

通过第一步可以看出,本地日志文件({user.home}/AppData/LocalLow/miHoYo/原神/output_log.txt)和断网刷新的请求API都不是真正获取祈愿数据的链接,需要将这些链接中的四个关键参数和额外拼接的四个参数组合在一起,才是真正获取数据的API接口

定义请求体(与API接口返回的字段相对应)

代码语言:javascript
复制
public class GenshinDataResponse {
    private int retcode;
    private String message;
    private GenshinDataPage data;
}
代码语言:javascript
复制
public class GenshinDataPage {
    private int page;
    private int size;
    private int total;
    private List<GenshinData> list;
}

API会返回一个Response,在Response中的data就是祈愿数据,其中每一个祈愿数据的属性与GenshinData实体想对应,存储在GenshinDataPage(分页数据)的list

构建API并解析数据

代码语言:javascript
复制
 /**
     * 通过祈愿接口获取祈愿数据
     *
     * @param api 祈愿接口
     * @return 祈愿数据
     */
    public static List<GenshinData> getData(String api) {
        String content = HttpRequest.get(api).execute().body();
        return Optional.ofNullable(JSON.parseObject(content, GenshinDataResponse.class))
                .map(GenshinDataResponse::getData)
                .map(GenshinDataPage::getList)
                .orElse(null);
    }

    /**
     * 获取祈愿的Api接口
     *
     * @param endId End数据ID
     * @param type  祈愿类型
     * @return Api接口
     */
    public static String getApi(long endId, GenshinDataType type) {
        return BASE_URL
                + DATA_API.substring(DATA_API.indexOf("?"))
                + "&gacha_type=" + type.getCode()
                + "&page=1&size=20&end_id="
                + endId;
    }

此处的DATA_API就是断网刷新获得的URL或者是本地日志文件中的URL,给这个URL拼接关键的四个参数即可,多余的参数也不需要删除,服务器不会处理

第三步:保存数据

这里既可以选择保存在内存中(Map),也可以保存在RedisMySQL数据库中,也可以序列化成JSON文件保存在磁盘中,各有优劣,本文不做展开,此处选择的是保存在Sqlite数据库中,防止数据过多造成内存溢出并支持永久保存

代码语言:javascript
复制
/**
     * 插入数据
     *
     * @param genshinData 祈愿记录
     */
    public void insert(GenshinData genshinData) {
        Entity entity = Entity.create(TABLE_NAME);
        entity.set("id", genshinData.getId());
        entity.set("uid", genshinData.getUid());
        entity.set("gacha_type", genshinData.getGacha_type());
        entity.set("item_id", genshinData.getItem_id());
        entity.set("count", genshinData.getCount());
        entity.set("time", genshinData.getTime());
        entity.set("name", genshinData.getName());
        entity.set("lang", genshinData.getLang());
        entity.set("item_type", genshinData.getItem_type());
        entity.set("rank_type", genshinData.getRank_type());
        try {
            Db.use(DatabaseSource.getDataSource()).insert(entity);
            Log.get().info("保存数据成功[{}][{}][{}]",genshinData.getId(), genshinData.getTime(), genshinData.getName());
        } catch (SQLException e) {
            Log.get().error("保存数据出现错误[{}][{}]", genshinData, e.getMessage());
            throw new RuntimeException(e);
        }
    }
代码语言:javascript
复制
public enum GenshinDataType {
    TYPE1(100, "新手祈愿"),
    TYPE2(200, "常驻祈愿"),
    TYPE3(301, "活动祈愿"),
    TYPE4(302, "武器祈愿");

    private final int code;
    private final String name;

    GenshinDataType(int code, String name) {
        this.code = code;
        this.name = name;
    }

    public int getCode() {
        return this.code;
    }

    public String getName() {
        return this.name;
    }
}
代码语言:javascript
复制
for (GenshinDataType value : GenshinDataType.values()) {
            Log.get().info("开始同步[{}]祈愿类型数据", value.getName());
            List<GenshinData> data = getData(getApi(0, value));
            while (!CollUtil.isEmpty(data)) {
                for (GenshinData genshinData : data) {
                    // 避免重复保存数据
                    if (CONNECTION.exists(genshinData.getId())) {
                        continue;
                    }
                    CONNECTION.insert(genshinData);
                }
                data = getData(getApi(data.get(data.size() - 1).getId(), value));
                Log.get("等待数据同步[1000ms]");
                ThreadUtil.safeSleep(1000);
            }
            Log.get().info("[{}]祈愿类型数据同步完成", value.getName());
        }
  • 运行单元测试,可以看到可以成功保存数据,且数据与官网数据一致
在这里插入图片描述
在这里插入图片描述

第四步:数据分析

这一步每一个人的需求不一样,属于动态的业务需求,本人仅需要查看历史五星、四星出货量,平均出五星的抽卡次数,以及各池子历史出货次数。如果想二次开发的,还可以增加指定物品名称,查看出货时间以及命座数

代码语言:javascript
复制
 /**
     * 通过祈愿物品级别查询数量
     *
     * @param rankType 祈愿物品级别
     * @return 此级别下的物品数量
     */
    public int queryRankTypeCount(GenshinRankType rankType) {
        String sql = "SELECT COUNT(*) FROM " + DatabaseConnection.TABLE_NAME + " WHERE rank_type = ?";
        return CONNECTION.count(sql, rankType.getCode());
    }
代码语言:javascript
复制
/**
     * 查询祈愿数据总数
     *
     * @return 总数量
     */
    public int queryTotal() {
        String sql = "SELECT COUNT(*) FROM " + DatabaseConnection.TABLE_NAME;
        return CONNECTION.count(sql);
    }
代码语言:javascript
复制
/**
     * 通过祈愿类型查询数量
     *
     * @param type 祈愿类型
     * @return 此祈愿类型的总数量
     */
    public int queryGenshinDataTypeCount(GenshinDataType type) {
        String sql = "SELECT COUNT(*) FROM " + DatabaseConnection.TABLE_NAME + " WHERE gacha_type = ?";
        return CONNECTION.count(sql, type.getCode());
    }
代码语言:javascript
复制
   /**
     * 通过祈愿类型和祈愿物品级别查询数量
     *
     * @param type     祈愿类型
     * @param rankType 祈愿物品级别
     * @return 当前祈愿类型下的,此祈愿物品级别的总数量
     */
    public int queryGenshinDataTypeAndRankTypeCount(GenshinDataType type, GenshinRankType rankType) {
        String sql = "SELECT COUNT(*) FROM " + DatabaseConnection.TABLE_NAME + " WHERE gacha_type = ? AND rank_type = ?";
        return CONNECTION.count(sql, type.getCode(), rankType.getCode());
    }
代码语言:javascript
复制
 /**
     * 通过祈愿类型查询最近一次抽出五星物品后的抽卡数量
     *
     * @param dataType 祈愿类型
     * @return 最近一次抽出五星物品后的抽卡数量
     */
    public int queryGenshinDataTypeByOrderCount(GenshinDataType dataType) {
        String sql = "select count(*) from genshin_data where gacha_type = ? " +
                "and id > ifnull((select id from genshin_data where rank_type = 5 and gacha_type = ? order by id desc limit 1), 0)";
        return CONNECTION.count(sql, dataType.getCode(), dataType.getCode());
    }
代码语言:javascript
复制
 /**
     * 通过祈愿类型和祈愿物品级别查询祈愿物品名称
     *
     * @param type     祈愿类型
     * @return 祈愿物品的名称集合
     */
    public List<String> queryData(GenshinDataType type) {
        String sql = "SELECT id, name, time FROM " + DatabaseConnection.TABLE_NAME + " WHERE gacha_type = ? AND rank_type = 5 ORDER BY id";
        List<Entity> result = CONNECTION.find(sql, type.getCode());
        if (CollUtil.isEmpty(result)) {
            return new ArrayList<>();
        }
        List<Integer> countList = new ArrayList<>(result.size());
        List<String> names = new ArrayList<>(result.size());
        for (int i = 0; i < result.size(); i++) {
            // 当前五星的名称
            String name = (String) result.get(i).get("name");
            // 当前抽取时间
            String time = (String) result.get(i).get("time");
            // 当前五星的ID
            long id = (long) result.get(i).get("id");
            // 上一个五星的ID
            long lastId = (i > 0) ? (long) result.get(i - 1).get("id") : 0L;
            String countSql = "SELECT COUNT(*) FROM " + DatabaseConnection.TABLE_NAME + " WHERE ID > ? and ID <= ? and gacha_type = ?";
            // 抽取五星的次数
            int count = CONNECTION.count(countSql, lastId, id, type.getCode());
            countList.add(count);
            // 保存每次抽取五星的次数
            names.add(name + "[第" + count + "次祈愿][" + time.substring(0, 10) + "]");
        }
        RANK_TYPE5.put(type, countList);
        return names;
    }
总结

整体架构没什么好说的,属于普通的增删查改操作,此处没有选择SpringBoot+Mybatis,改用Hutool工具库提供的数据库操作工具,也没有编写前端展示页面,而是控制台输出结果并将结果保存在文件中,便于随时查看。

可优化地方:

  1. 本工具暂时仅考虑了手动粘贴断网刷新后的请求API到配置文件中,其实可以利用Hutool的提供的FileWatcher自动监听用户目录下的日志文件,并过滤出请求参数,手动拼接时间戳,这样即使API时间过期也可以自动生成新的请求了。如果后续别的小伙伴使用的话,可以考虑做一个。
  2. 没有编写前端页面,这个也就是一两天的事情。如果有需求,再考虑。
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022-07-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 手写一个原神祈愿分析工具
    • 第一步:解析请求数据Api
      • 第二步:API请求链接转换以及解析数据
        • 第三步:保存数据
          • 第四步:数据分析
            • 总结
        相关产品与服务
        数据库
        云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档