苹果内购-WWDC

苹果内购-WWDC

App Store Server API

苹果提供了以下这些 Server API

API简介


查询用户订单的收据

GET https://api.storekit.itunes.apple.com/inApps/v1/lookup/{orderId}

Jingxc大约 9 分钟payjava后端payjavaapplewwdc后端
google内购

google内购

google支付需要在服务端记录并验证订单,防止伪造订单,这里记录一下服务端校验订单

Google Pay主要支付流程:

相关信息

  • 手机端向Java服务端发起支付,生成预订单,给手机端返回生成的订单号
  • 手机端向Google发起支付(传入java服务端生成的订单号)
  • Google服务器将支付结果返回给手机端(因这边用到的是消耗型的产品,所以购买后必须要通知gp我已经消耗了这次交易)
  • 手机端向Java服务端发送校验请求,校验通过后即可处理订单(服务端重试校验,发货,保证订单正常发货成功)

Jingxc大约 14 分钟payjava后端payjavagoogle后端
Mybatis源码

Mybatis源码

1. 手写持久层框架思路

1.1 框架使用端(项目)


引入自定义持久层框架jar包

  • 创建SqlMapConfig.xml配置文件:数据库配置信息,(存放mapper.xml的路径地址)
  • 创建mapper.xml配置文件:存放sql信息,参数类型,返回值类型

1.2 框架本身


本质是对JDBC进行封装

  1. 加载配置文件

    • 创建Resources类,负责加载配置文件,加载成字节数入流,存放到内存中
    • 方法:InputStream getResourceAsStream(String path)
  2. 创建两个JavaBean(容器对象)

    • Configuration:全局配置类,存储sqlMapConfig.xml配置文件解析出来的内容
    • MappedStatement:映射配置类:存储mapper.xml配置文件解析出来的内容
  3. 解析配置文件,填充容器对象

    • 创建SqlSessionFactoryBuilder类
      • 方法:SqlSessionFactory
      • build(InputStream)(1)解析配置文件(dom4j+xpath)封装Configuration,(2) 创建SqlSessionFactory
  4. 创建SqlSessionFactory接口及DefaultSqlSessionFactory

    • 方法:SqlSession openSession() 工厂模式
  5. 创建SqlSession接口和DefaultSqlSession实现类

    • 方法:selectList()...
  6. 创建Executor接口和实现类SimpleExecutor

    • 方法:query(Configuration,MappedStatement,Object),执行底层JDBC代码(数据库和sql配置信息)

Jingxc大约 2 分钟mybatisjava后端javamybatis后端
ConcurrentHashMap

ConcurrentHashMap

1. 构造器

ConcurrentHashMap的构造器和HashMap的构造器基本相同

2. put方法

ConcurrentHashMap键或者值为空会抛出空指针异常,put方法开始先求取hash

final V putVal(K key, V value, boolean onlyIfAbsent) {
    if (key == null || value == null) throw new NullPointerException();
    int hash = spread(key.hashCode());
    ...
}

static final int spread(int h) {
    return (h ^ (h >>> 16)) & HASH_BITS;
}

Jingxc大约 8 分钟java后端ConcurrentHashMapjava后端
HashMap-02

HashMap-02

1. 构造器

HashMap在创建对象时,可的调用构造器有三种,无参构造器(常用),初始化容量大小,初始化容量和加载因子

初始化的时候,

  • 默认容量为16,static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
  • 默认最大容量为1<<30(高位符号位), static final int MAXIMUM_CAPACITY = 1 << 30;
  • 加载因子,static final float DEFAULT_LOAD_FACTOR = 0.75f;

Jingxc大约 6 分钟java后端HashMapjava后端
游戏术语

游戏术语

概念

当谈论游戏数据时,以下是一些常见的术语和概念:

留存率(Retention Rate):

留存率是指在特定时间段内,玩家继续留在游戏中的百分比。通常以日留存率(玩家在游戏中连续两天或更多天的百分比)和周留存率(玩家在游戏中连续一周或更多周的百分比)来衡量。

生命价值(Lifetime Value,LTV):

LTV是指玩家在其整个游戏生命周期内为游戏公司带来的预期收入。它通常考虑了玩家的购买习惯、留存率和付费金额等因素。


Jingxc大约 3 分钟java后端java后端
Log日志

Log日志

在完成日常任务时,有时候需要根据不同的目的,自己定制化完成log日志的输出,通过配置文件实现日志输出存在一定的局限性,可以通过代码来,定制化实现下面的目标

  • 自定义日志文件输出目录
  • 自定义日志文件名称格式
  • 自定义日志文件输出间隔
  • 自定义日志内容输出格式 ...

配置文件

package com.game.server.config;

import java.text.SimpleDateFormat;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.FileAppender;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.PatternLayout;
import org.apache.log4j.varia.LevelRangeFilter;

public class LoggerFactory {

    // 防止频繁创建Logger对象
    private static volatile Map<String, List<Object>> logMap = new ConcurrentHashMap<String, List<Object>>();

    public static Logger getLogger(String baseDir, String appId, String type) {

        SimpleDateFormat formatter = new SimpleDateFormat("'_'yyyyMMdd");
        String date = formatter.format(new Date());

        if (StringUtils.isEmpty(baseDir) || StringUtils.isEmpty(appId)) {
            throw new IllegalArgumentException("cannot be empty");
        }
        // 防止map过大不按时间存储{ key: [date_A, logger_A] }
        String key = baseDir + "/" + appId + "/gameserver_" + appId + "_" + type;

        List<Object> list = logMap.get(key);

        if (list == null) {
            // 存储新数据
            try {

                Logger logger = createLogger(key, type, date);
                return logger;

            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        // 比较时间
        String saveDate = String.valueOf(list.get(0));
        if (saveDate.equals(date)) {
            Logger logger = (Logger) list.get(1);
            return logger;
        } else {
            try {
                // 更新数据
                Logger logger = createLogger(key, type, date);
                return logger;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * 添加同步锁,并进行双端校验,防止重复创建
     * 
     * @param key
     * @param type
     * @param date
     * @return
     */
    private synchronized static Logger createLogger(String key, String type, String date) {

        // 双端检索,防止重复创建
        List<Object> list = logMap.get(key);

        if (list != null) {
            String saveDate = String.valueOf(list.get(0));
            if (saveDate.equals(date)) {
                Logger logger = (Logger) list.get(1);
                return logger;
            }
        }

        String logFilePath = key + date;
        // 添加Appender到日志记录器
        Logger logger = Logger.getLogger("com.game.server.config.LoggerFactory." + type + date);

        FileAppender appender = new FileAppender();

        // 设置输出文件名
        appender.setFile(logFilePath);
        // 设置输出格式
        appender.setLayout(new PatternLayout("%m%n"));
        // 设置Appender的阈值级别
        appender.setThreshold(Level.INFO);
        appender.activateOptions();

        LevelRangeFilter filterInfo = new LevelRangeFilter();
        filterInfo.setLevelMin(Level.INFO);
        filterInfo.setLevelMax(Level.ERROR);
        appender.addFilter(filterInfo);
        logger.addAppender(appender);

        list = new ArrayList<>();
        list.add(date);
        list.add(logger);

        logMap.put(key, list);

        return logger;
    }
}


Jingxc大约 3 分钟java后端logjava后端
动态数据源

动态数据源

在工作中,我们可能会遇到下面这些情况,如果只是在配置文件中配置数据源就有可能太繁琐,或者无法满足要求

  1. 需要配置不止一个数据源,数据源的链接信息都不相同
  2. 需要在服务运行过程中,动态的添加,减少,切换所使用的数据源
  3. 同一服务中不同的接口服务需要调用不同的数据源信息

1.创建当前线程工具类

通过当前线程工具类,保留当前线程数据源信息,防止数据源切换时导致数据错误

package com.game.server.source;

import lombok.extern.log4j.Log4j;

/**
 * 保留当前线程数据源信息
 * 
 */
@Log4j
public class DataSourceContextHolder {

    /**
     * 线程级别的私有变量
     */
    private static final ThreadLocal<String> CONTEXTHOLDER = new ThreadLocal<>();

    /**
     * 切换数据源
     */
    public static void setDataSource(String datasourceId) {
        CONTEXTHOLDER.set(datasourceId);
        log.info("已切换到数据源:{}" + datasourceId);
    }

    public static String getDataSource() {
        return CONTEXTHOLDER.get();
    }

    /**
     * 删除数据源
     */
    public static void removeDataSource() {
        CONTEXTHOLDER.remove();
        log.info("已切换到主数据源");
    }

}

Jingxc大约 8 分钟java后端java后端Mysql
2
3
4