短视频app
一. 创建项目
1.1 前言
项目使用的工具:
- MAC M1:苹果M1芯片笔记本
- HBuilderX:用于app开发
- IDEA:用于后端开发
- PhpWebStudy:用于数据库,Redis,静态资源文件等控制
项目使用语言极其版本:
- 后端:jdk1.8,mysql8,redis7.2.4
- 前端:vue3,uts,uni-app x
特别说明
uni-app x,是下一代 uni-app,是一个跨平台应用开发引擎。
uni-app x 是一个庞大的工程,它包括uts语言、uvue渲染引擎、uni的组件和API、以及扩展机制。
uts是一门类ts的、跨平台的、新语言。uts在iOS端编译为swift、在Android端编译为kotlin、在Web端编译为js。
在Android平台,uni-app x 的工程被编译为kotlin代码,本质上是换了vue写法的原生kotlin应用,在性能上与原生kotlin一致。
Midjourney
1. Midjourney指令
指令 | 作用 | 指令 | 作用 | 指令 | 作用 |
---|---|---|---|---|---|
/imagine | 生成图像 | /settings | 查看机器人设置 | /info | 查看基本信息 |
/describe | 描述图片信息 | /relax | 切换到relaxed模式 | /fast | 切换到fast模式 |
/blend | 混合两个图像 | /ask | 提问(获得答案) | /help | 查看帮助信息 |
/stealth | 切换到隐身模式 | /public | 切换到公共模式 | /subscribe | 查看/管理订阅 |
/prefer option set | 创建一个自定义变量 | /prefer suffix | 指定要添加某个末尾的提示后缀 | /prefer option list | 列出之前设置的所有自定义变量 |
/show | 结合任务ID生成原图片 |
Mybatis源码
1. 手写持久层框架思路
1.1 框架使用端(项目)
引入自定义持久层框架jar包
- 创建SqlMapConfig.xml配置文件:数据库配置信息,(存放mapper.xml的路径地址)
- 创建mapper.xml配置文件:存放sql信息,参数类型,返回值类型
1.2 框架本身
本质是对JDBC进行封装
-
加载配置文件
- 创建Resources类,负责加载配置文件,加载成字节数入流,存放到内存中
- 方法:InputStream getResourceAsStream(String path)
-
创建两个JavaBean(容器对象)
- Configuration:全局配置类,存储sqlMapConfig.xml配置文件解析出来的内容
- MappedStatement:映射配置类:存储mapper.xml配置文件解析出来的内容
-
解析配置文件,填充容器对象
- 创建SqlSessionFactoryBuilder类
- 方法:SqlSessionFactory
- build(InputStream)(1)解析配置文件(dom4j+xpath)封装Configuration,(2) 创建SqlSessionFactory
- 创建SqlSessionFactoryBuilder类
-
创建SqlSessionFactory接口及DefaultSqlSessionFactory
- 方法:SqlSession openSession() 工厂模式
-
创建SqlSession接口和DefaultSqlSession实现类
- 方法:selectList()...
-
创建Executor接口和实现类SimpleExecutor
- 方法:query(Configuration,MappedStatement,Object),执行底层JDBC代码(数据库和sql配置信息)
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;
}
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;
游戏术语
概念
当谈论游戏数据时,以下是一些常见的术语和概念:
留存率(Retention Rate):
留存率是指在特定时间段内,玩家继续留在游戏中的百分比。通常以日留存率(玩家在游戏中连续两天或更多天的百分比)和周留存率(玩家在游戏中连续一周或更多周的百分比)来衡量。
生命价值(Lifetime Value,LTV):
LTV是指玩家在其整个游戏生命周期内为游戏公司带来的预期收入。它通常考虑了玩家的购买习惯、留存率和付费金额等因素。
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;
}
}
动态数据源
在工作中,我们可能会遇到下面这些情况,如果只是在配置文件中配置数据源就有可能太繁琐,或者无法满足要求
- 需要配置不止一个数据源,数据源的链接信息都不相同
- 需要在服务运行过程中,动态的添加,减少,切换所使用的数据源
- 同一服务中不同的接口服务需要调用不同的数据源信息
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("已切换到主数据源");
}
}