短视频app

短视频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一致。


Jingxc大约 4 分钟uni-appjavaappjavauni-appapp
Midjourney

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生成原图片

Jingxc大约 4 分钟MidjourneyMidjourney
uni-app项目

uni-app项目

1. 开发软件安装

推荐:HBuilderX

安装文档:参照

2. 创建项目

按照官网提供的教程新建项目即可参照


Jingxc大约 4 分钟前端uni-app小程序前端templateh5小程序
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
5