苹果内购-WWDC
App Store Server API
苹果提供了以下这些 Server API
API简介
查询用户订单的收据
GET https://api.storekit.itunes.apple.com/inApps/v1/lookup/{orderId}
苹果提供了以下这些 Server API
查询用户订单的收据
GET https://api.storekit.itunes.apple.com/inApps/v1/lookup/{orderId}
还是先找官方文档...
google支付需要在服务端记录并验证订单,防止伪造订单,这里记录一下服务端校验订单
Google Pay主要支付流程:
相关信息
引入自定义持久层框架jar包
本质是对JDBC进行封装
加载配置文件
创建两个JavaBean(容器对象)
解析配置文件,填充容器对象
创建SqlSessionFactory接口及DefaultSqlSessionFactory
创建SqlSession接口和DefaultSqlSession实现类
创建Executor接口和实现类SimpleExecutor
ConcurrentHashMap的构造器和HashMap的构造器基本相同
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;
}
HashMap在创建对象时,可的调用构造器有三种,无参构造器(常用),初始化容量大小,初始化容量和加载因子
初始化的时候,
当谈论游戏数据时,以下是一些常见的术语和概念:
留存率(Retention Rate):
留存率是指在特定时间段内,玩家继续留在游戏中的百分比。通常以日留存率(玩家在游戏中连续两天或更多天的百分比)和周留存率(玩家在游戏中连续一周或更多周的百分比)来衡量。
生命价值(Lifetime Value,LTV):
LTV是指玩家在其整个游戏生命周期内为游戏公司带来的预期收入。它通常考虑了玩家的购买习惯、留存率和付费金额等因素。
在完成日常任务时,有时候需要根据不同的目的,自己定制化完成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;
}
}
在工作中,我们可能会遇到下面这些情况,如果只是在配置文件中配置数据源就有可能太繁琐,或者无法满足要求
通过当前线程工具类,保留当前线程数据源信息,防止数据源切换时导致数据错误
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("已切换到主数据源");
}
}