【Java】将枚举类转换为Redis字典缓存
字典翻译框架实现看这篇:
https://www.cnblogs.com/mindzone/p/16890632.html
枚举的特性
首先是枚举的一些特性:
1、枚举实例直接在枚举类中声明
2、重载构造器,可以直观表示枚举的属性信息
3、枚举类的方法:
- values 可以获取枚举类的所有枚举项(数组)
- 枚举实例 ordinal() 方法获取枚举的下标值
- 枚举实例 name() 方法 获取你声明的实例名
4、枚举是完全单例的
5、枚举不可以作为通常对象响应给Web传数据,必须自己提取信息转换
package cn.hyite.amerp.common.state; import cn.hyite.amerp.common.state.intf.IEnumStateConvert;
import lombok.Getter; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; /**
* 盖印状态枚举类
* @author OnCloud9
* @version 1.0
* @project amerp-server
* @date 2022年11月23日 13:31
*/
@Getter
public enum StampStateEnum implements IEnumStateConvert {
UNUSED("未盖印", "0"),
DONE("已盖印", "1"),
CANCEL("已取消", "2"),
; public static final String CATE = "STAMP_STATE";
private final String code;
private final String name; StampStateEnum(String name, String code) {
this.name = name;
this.code = code;
} @SuppressWarnings("all")
@Override
public Map<String, String> getItem() {
Map<String, String> instance = new ConcurrentHashMap<>();
instance.put("instName", name());
instance.put("instIndex", String.valueOf(ordinal()));
instance.put("name", name);
instance.put("code", code);
return instance;
}
}
基于枚举,我们可以设置字典的属性,相比普通类更直观的表现【字典】
UNUSED("未盖印", "0"),
DONE("已盖印", "1"),
CANCEL("已取消", "2"),
我最初的设计几乎和DictDTO没区别,关键字段是 字典编号,字典名称,字典类别
存储方式就直接在应用中,不需要Redis,缓存的抽象设计沿用Redis的方案
package cn.cloud9.server.test.model; import lombok.AllArgsConstructor;
import lombok.Getter;
import org.apache.commons.collections.CollectionUtils; import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author OnCloud9
* @description
* @project tt-server
* @date 2022年11月06日 下午 06:25
*/
@Getter
@AllArgsConstructor
public enum MyType { GROUP1_TYPE1("a类型1", 1001, MyType.KEY_GP1),
GROUP1_TYPE2("a类型2", 1002, MyType.KEY_GP1),
GROUP1_TYPE3("a类型3", 1003, MyType.KEY_GP1), GROUP2_TYPE1("b类型1", 1001, MyType.KEY_GP2),
GROUP2_TYPE2("b类型2", 1002, MyType.KEY_GP2),
GROUP2_TYPE3("b类型3", 1003, MyType.KEY_GP2), GROUP3_TYPE1("c类型1", 1001, MyType.KEY_GP3),
GROUP3_TYPE2("c类型2", 1002, MyType.KEY_GP3),
GROUP3_TYPE3("c类型3", 1003, MyType.KEY_GP3),
; private final String name;
private final Integer value;
private final String cate; private static final Map<String, List<MyType>> typeList = new ConcurrentHashMap<>();
private static final Map<String, String> typeMap = new ConcurrentHashMap<>(); public static final String SEPARATOR = "@";
public static final String KEY_GP1 = "GT-1001";
public static final String KEY_GP2 = "GT-1002";
public static final String KEY_GP3 = "GT-1003"; static {
for (MyType myType : MyType.values()) {
final String myTypeCate = myType.getCate();
final String myTypeName = myType.getName();
final Integer myTypeValue = myType.getValue(); final String key = myTypeCate + SEPARATOR + myTypeValue;
typeMap.put(key, myTypeName); List<MyType> myTypes = typeList.get(myTypeCate);
if (CollectionUtils.isEmpty(myTypes)) {
myTypes = new ArrayList<>();
typeList.put(myTypeCate, myTypes);
}
myTypes.add(myType);
}
} /**
* 按枚举类别获取枚举集合
* @param cate
* @return
*/
public static List<MyType> getTypeListByCate(String cate) {
return typeList.get(cate);
} /**
* 按枚举类别获取枚举集合(响应用)
* @param cate
* @return
*/
public static List<Map<String, String>> getItemListByCate(String cate) {
final List<MyType> myTypes = typeList.get(cate);
if (CollectionUtils.isEmpty(myTypes)) return Collections.EMPTY_LIST;
final ArrayList<Map<String, String>> items = new ArrayList<>(myTypes.size());
for (MyType myType : myTypes) {
items.add(myType.getItem());
}
return items;
} /**
* 按枚举类别和code获取名称
* @param cate
* @param code
* @return
*/
public static String getNameBy(String cate, Integer code) {
String key = cate + SEPARATOR + code;
return typeMap.get(key);
} public Map<String, String> getItem() {
Map<String, String> item = new ConcurrentHashMap<>();
item.put("instName", name());
item.put("instIndex", String.valueOf(this.ordinal()));
item.put("name", this.name);
item.put("value", String.valueOf(this.value));
return item;
}
}
但是老板认为一个枚举类不应该枚举所有的状态种类,一个枚举类就只负责一个特定状态类别:
例如:
1、审批状态
2、盖印状态
3、借还状态
翻译交给另外一个工具类来完成
加上我们已经习惯字典翻译框架的开发方式
在尽可能不动框架的情况下,就通过工具类来完成:
package cn.hyite.amerp.common.util; import cn.hyite.amerp.common.state.ApproveStateEnum;
import cn.hyite.amerp.common.state.LendStateEnum;
import cn.hyite.amerp.common.state.StampStateEnum;
import cn.hyite.amerp.system.common.dict.dto.DictDTO;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.StringRedisTemplate; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function; /**
* 枚举缓存工具类,沿用Redis字典缓存方式初始化缓存
* @author OnCloud9
* @version 1.0
* @project amerp-server
* @date 2022年11月24日 09:42
*/
public class StateEnumUtil {
private static final String DICT_CACHE_LISTS = "dict_cache_lists";
private static final String DICT_CACHE_KEYS = "dict_cache_keys";
private static final String SEPARATOR = "|";
private static final String DEFAULT_TABLE = "sys_co_dict";
private static final List<DictDTO> enumList = new ArrayList<>(); static {
List<DictDTO> approveStateList = transToDictList(ApproveStateEnum.values(), ApproveStateEnum::getCode, ApproveStateEnum::getName, ApproveStateEnum.CATE);
List<DictDTO> stampStateList = transToDictList(StampStateEnum.values(), StampStateEnum::getCode, StampStateEnum::getName, StampStateEnum.CATE);
List<DictDTO> lendStateList = transToDictList(LendStateEnum.values(), LendStateEnum::getCode, LendStateEnum::getName, LendStateEnum.CATE); enumList.addAll(approveStateList);
enumList.addAll(stampStateList);
enumList.addAll(lendStateList);
} public static void initialize() {
/* 1、获取RedisTemplate */
StringRedisTemplate stringRedisTemplate = SpringContext.getBean(StringRedisTemplate.class);
HashOperations<String, Object, Object> opsForHash = stringRedisTemplate.opsForHash(); /* 2、准备redis装载容器 */
Map<String, String> tmpMap = new HashMap<>();
Map<String, List<DictDTO>> tmpListMap = new HashMap<>(); /* 3、将枚举按字典的方式拼装 */
for (DictDTO dto : enumList) {
String diCateIdent = dto.getDiCateIdent();
String diCode = dto.getDiCode(); /* hKey -> 字典表名 | 代码类别 | 代码编号 */
String key = DEFAULT_TABLE + SEPARATOR + diCateIdent + SEPARATOR + diCode;
tmpMap.put(key.toUpperCase(), GsonUtil.toJson(dto)); /* hKey -> 字典表名 | 代码类别 */
String key2 = DEFAULT_TABLE + SEPARATOR + diCateIdent;
List<DictDTO> list = tmpListMap.get(key2.toUpperCase());
if (CollectionUtils.isEmpty(list)) {
list = new ArrayList<>();
tmpListMap.put(key2.toUpperCase(), list);
}
list.add(dto);
} /* 4、推入Redis中,不做删除操作,共用缓存Key */
opsForHash.putAll(DICT_CACHE_KEYS, tmpMap);
opsForHash.putAll(DICT_CACHE_LISTS, toJsonMap(tmpListMap));
LogUtil.COUNT_LOG.info("加载枚举缓存字典到Redis完毕...");
} private static Map<String, String> toJsonMap(Map<String, ?> tmpListMap) {
Map<String, String> jsonList = new HashMap<>(tmpListMap.size());
for (Map.Entry<String, ?> entry : tmpListMap.entrySet()) {
jsonList.put(entry.getKey(), GsonUtil.toJson(entry.getValue()));
}
return jsonList;
} /**
* 将枚举类集合转换为字典集合
* @param eArr 枚举类数组
* @param diCodeFunc 设置diCode取值方法
* @param diNameFunc 设置diName取值方法
* @param diCateIdent 设置diCateIdent值
* @return List<DictDTO>
* @author OnCloud9
* @date 2022/11/24 10:40
*/
private static <StateEnum extends Enum, FieldType> List<DictDTO> transToDictList(StateEnum[] eArr, Function<StateEnum, FieldType> diCodeFunc, Function<StateEnum, FieldType> diNameFunc, String diCateIdent) {
List<DictDTO> dictList = new ArrayList<>(eArr.length);
for (StateEnum e : eArr) {
DictDTO dict = new DictDTO();
/* 1、设置字段编码 */
FieldType diCode = diCodeFunc.apply(e);
dict.setDiCode(String.valueOf(diCode)); /* 2、设置字段名称 */
FieldType diName = diNameFunc.apply(e);
dict.setDiName(String.valueOf(diName)); /* 3、设置字段类别 */
dict.setDiCateIdent(diCateIdent); /* 4、装载 */
dictList.add(dict);
}
return dictList;
}
}
在处理的钩子方法这里加上工具类的初始化:
package cn.hyite.amerp.common.cache; import cn.hyite.amerp.common.cache.impl.RedisCacheDictServiceImpl;
import cn.hyite.amerp.common.cache.intf.CacheDictService;
import cn.hyite.amerp.common.util.SpringContext;
import cn.hyite.amerp.common.util.StateEnumUtil; /**
* 业务数据缓存管理
*
* @version 1.0
* @project portal-server
* @author lianss
* @date 2019年2月25日 下午6:19:36
*/
public abstract class CacheManager { /**
* 刷新缓存
*
* @author lianss
* @date :2019年2月25日 下午6:24:49
*/
public static void refreshCache() {
Thread refreshThread = new Thread(new RefreshCache());
refreshThread.start();
}
} class RefreshCache extends Thread {
@Override
public void run() {
CacheDictService cacheDictService = SpringContext.getBean(RedisCacheDictServiceImpl.class);
cacheDictService.init();
StateEnumUtil.initialize();
}
}
这里的难题主要是有各种各样的枚举类,需要有一个统一的方法来转换
提供枚举类,和字典属性的取值方法,可以生成字典集合
但是我不会写Lambda方法入参啊。。。,然后参考了一下MybatisPlus的lambdaQuery()源码,写出来了,
/**
* 将枚举类集合转换为字典集合
* @param eArr 枚举类数组
* @param diCodeFunc 设置diCode取值方法
* @param diNameFunc 设置diName取值方法
* @param diCateIdent 设置diCateIdent值
* @return List<DictDTO>
* @author OnCloud9
* @date 2022/11/24 10:40
*/
private static <StateEnum extends Enum, FieldType> List<DictDTO> transToDictList(StateEnum[] eArr, Function<StateEnum, FieldType> diCodeFunc, Function<StateEnum, FieldType> diNameFunc, String diCateIdent) {
List<DictDTO> dictList = new ArrayList<>(eArr.length);
for (StateEnum e : eArr) {
DictDTO dict = new DictDTO();
/* 1、设置字段编码 */
FieldType diCode = diCodeFunc.apply(e);
dict.setDiCode(String.valueOf(diCode)); /* 2、设置字段名称 */
FieldType diName = diNameFunc.apply(e);
dict.setDiName(String.valueOf(diName)); /* 3、设置字段类别 */
dict.setDiCateIdent(diCateIdent); /* 4、装载 */
dictList.add(dict);
}
return dictList;
}
2022年12月1日 22点22分 更新
解决手动指定字段,改用注解实现:
-- 字典类别标记
package cn.cloud9.server.struct.enums.annotation; import java.lang.annotation.*; @Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DictCate {
} -- 字典编号标记
package cn.cloud9.server.struct.enums.annotation; import java.lang.annotation.*; @Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DictCode {
} -- 字典名称标记
package cn.cloud9.server.struct.enums.annotation; import java.lang.annotation.*; @Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DictName {
}
解决手动编码加载的问题:
package cn.cloud9.server.struct.enums.hook; import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternUtils;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;
import org.springframework.util.SystemPropertyUtils; import java.util.HashSet;
import java.util.Set; /**
* @author OnCloud9
* @description
* @project tt-server
* @date 2022年11月27日 下午 10:43
*/
@Slf4j
@Component
public class ScanSupport implements ResourceLoaderAware { private ResourceLoader resourceLoader;
private final ResourcePatternResolver resolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
private final MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader); @Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
} /**
* 扫描公共接口包,获取所有的公共接口并加入白名单
*
*/
@SneakyThrows
public Set<Class<?>> doScan(String classPath) {
Set<Class<?>> classes = new HashSet<>();
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
.concat(ClassUtils.convertClassNameToResourcePath(SystemPropertyUtils.resolvePlaceholders(classPath))
.concat("/**/*.class"));
Resource[] resources = resolver.getResources(packageSearchPath);
MetadataReader metadataReader = null;
for (Resource resource : resources) {
if (!resource.isReadable()) continue;
metadataReader = metadataReaderFactory.getMetadataReader(resource);
try {
classes.add(Class.forName(metadataReader.getClassMetadata().getClassName()));
} catch (Exception e) {
log.info("公共接口信息解析错误:{}", e.getMessage());
}
}
return classes;
}
}
按指定包下加载所有类,根据注解标记装填进入Redis:
package cn.cloud9.server.struct.enums; import cn.cloud9.server.struct.dict.dto.DictDTO;
import cn.cloud9.server.struct.enums.annotation.DictCate;
import cn.cloud9.server.struct.enums.annotation.DictCode;
import cn.cloud9.server.struct.enums.annotation.DictName;
import cn.cloud9.server.struct.enums.hook.ScanSupport;
import cn.cloud9.server.struct.lambda.ObjectBuilder;
import cn.cloud9.server.struct.spring.SpringContextHolder;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate; import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap; import static cn.cloud9.server.struct.dict.service.DictService.*; /**
* @author OnCloud9
* @description
* @project tt-server
* @date 2022年11月27日 下午 08:53
*/
@Slf4j
@SuppressWarnings("all")
public class EnumUtil {
private static final List<Class> enumClass = new ArrayList<>();
private static final String CLASS_SCAN_PACKAGE_PATH = "cn.cloud9.server.struct.enums.state"; @SneakyThrows
public static void findEnumClassByPath() {
if (CollectionUtils.isNotEmpty(enumClass)) return;
try {
final ScanSupport scanSupport = SpringContextHolder.getBean(ScanSupport.class);
final Set<Class<?>> classes = scanSupport.doScan(CLASS_SCAN_PACKAGE_PATH);
enumClass.addAll(classes);
} catch (Exception e) {
e.printStackTrace();
}
} public static void initialize() {
findEnumClassByPath(); RedisTemplate<String, Map<String, String>> mapTemplate = SpringContextHolder.getBean("redisTemplate", RedisTemplate.class);
final HashOperations<String, Object, Object> hashOps = mapTemplate.opsForHash(); String sqlKey = "default";
/* 准备缓存结构容器, 并装载数据 */
Map<String, String> mapTank = new ConcurrentHashMap<>();
Map<String, List<DictDTO>> listTank = new ConcurrentHashMap<>(); final List<DictDTO> dicts = transEnumsToDicts();
for (DictDTO dict : dicts) {
final Long dictCode = dict.getDictCode();
final String dictName = dict.getDictName();
final String dictType = dict.getDictType(); /* 装载 key -> h-key -> h-value */
final String mapKey = sqlKey + SEPARATOR + dictType + SEPARATOR + dictCode;
mapTank.put(mapKey, dictName); /* 装载 key -> h-key -> h-list */
final String listKey = sqlKey + SEPARATOR + dictType;
List<DictDTO> cateList = listTank.get(listKey);
if (CollectionUtils.isEmpty(cateList)) {
cateList = new ArrayList<>();
listTank.put(listKey, cateList);
}
cateList.add(dict);
} /* 装填到Redis中 */
hashOps.putAll(KEY_MAP, mapTank);
hashOps.putAll(KEY_LISTS, listTank); log.info("Redis 枚举缓存装载完毕 ...... ");
} /**
* 将声明的枚举集合转换为字典集合
* @return
*/
@SneakyThrows
private static List<DictDTO> transEnumsToDicts() {
final List<DictDTO> dicts = new ArrayList<>();
for (Class enumClass : enumClass) {
if (!enumClass.isEnum()) continue;
final String cateName = enumClass.getSimpleName();
final Object[] enumInstances = enumClass.getEnumConstants();
for (Object inst : enumInstances) { /* 反射,寻找注解的编号字段 */
final Field codeField = findFieldByAnnotaion(enumClass, DictCode.class);
final Object codeVal = codeField.get(inst); /* 反射,寻找注解的名字字段 */
final Field nameField = findFieldByAnnotaion(enumClass, DictName.class);
final Object nameVal = nameField.get(inst); /* 反射,寻找注解的类别字段 */
final Field cateField = findFieldByAnnotaion(enumClass, DictCate.class);
final Object cateVal = cateField.get(inst); DictDTO dto = ObjectBuilder
.<DictDTO>builder(DictDTO::new)
.with(DictDTO::setDictType, String.valueOf(cateVal))
.with(DictDTO::setDictCode, Long.valueOf(String.valueOf(codeVal)))
.with(DictDTO::setDictName, String.valueOf(nameVal))
.build();
dicts.add(dto);
}
}
return dicts;
} /**
* 获取枚举类中标记了指定注解的字段
* @param targetClass 枚举目标类对象
* @param annotationClass 声明的注解类对象
* @param <T> 注解泛型
* @param <E> 枚举泛型
* @return 被注解的字段
*/
@SneakyThrows
private static <T extends Annotation, E extends Enum> Field findFieldByAnnotaion(Class<E> targetClass, Class<T> annotationClass) {
final Field targetField = Arrays
.stream(targetClass.getDeclaredFields())
.filter(field -> Objects.nonNull(field.getAnnotation(annotationClass)))
.findFirst()
.orElse(null);
if (Objects.isNull(targetField)) throw new RuntimeException(targetClass.getName() + " 没有声明@" + annotationClass.getName() + "注解!!加载失败!!!");
targetField.setAccessible(true);
return targetField;
}
}
【Java】将枚举类转换为Redis字典缓存的更多相关文章
- Java学习——枚举类
Java学习——枚举类 摘要:本文主要介绍了Java的枚举类. 部分内容来自以下博客: https://www.cnblogs.com/sister/p/4700702.html https://bl ...
- Java笔记---枚举类和注解
Java笔记---枚举类和注解 一.枚举类 自定义枚举类 方式一:JDK5.0之前自定义枚举类 class Seasons { //1. 声明Seasons对象的属性 private final St ...
- Java系统高并发之Redis后端缓存优化
一:前端优化 暴露接口,按钮防重复(点击一次按钮后就变成禁用,禁止重复提交) 采用CDN存储静态化的页面和一些静态资源(css,js等) 二:Redis后端缓存优化 Redis 是完全开源免费的,遵守 ...
- 【Java】 枚举类
如果要定义一个枚举类: public enum Size { SAMLL, MEDIUM, LARGE, EXTRA, EXTRA_LARGE}; 实际上,这个声明定义的类型是一个类,它刚好有4个实例 ...
- java中枚举类的实际应用
知识点:在Java中,使用枚举类,当遇到实例类型有限的类时,并且数据库中用状态码代表一种含义时,如星期,性别,员工登陆某系统的状态等等, 可以考虑使用枚举类 本例子可以仿照,也可以使用自定义的类型处理 ...
- Java:枚举类也就这么回事
目录 一.前言 二.源自一道面试题 三.枚举的由来 四.枚举的定义形式 五.Enum类里有啥? 1.唯一的构造器 2.重要的方法们 3.凭空出现的values()方法 六.反编译枚举类 七.枚举类实现 ...
- java基础---枚举类与注解
一.枚举类 类的对象只有有限个,确定的.我们称此类为枚举类 如果枚举类中只有一个对象,则可以作为单例模式的实现方式. 定义枚举类 方式一:jdk5.0之前,自定义枚举类 public class Se ...
- 【Java】枚举类
文章目录 枚举类的使用 如何定义枚举类 方式一:jdk5.0之前,自定义枚举类 方式二:jdk5.0,可以使用enum关键字定义枚举类 Enum类的主要方法 toString() values() v ...
- java中枚举类的使用详解
/* * 通过JDK5提供的枚举来做枚举类 */ public enum Direction2 { FRONT("前"), BEHIND("后"), LEFT( ...
- Java之枚举类
有时候,变量的取值只在一个有限的集合内. 例如:pizza的大小只有小.中.大和超大这四种尺寸.当然,可以将这些尺寸分别编码为1.2.3.4或者S.M.L.X.但这样存在着一定的隐患.在变量中很有可能 ...
随机推荐
- C#笔记(1)窗体
1. 隐藏TabPage 在使用TabControl控件时,希望隐藏其中某个选项卡(即TabPage).设置该TabPage的父容器为null 即可,如TabPage.Parent = null .如 ...
- numpy基础--ndarray(一种多维数组对象)
NumPy基本介绍 NumPy(Numerical Python)是高性能科学计算和数据分析的基础包.其提供了以下基本功能: ndarray:一种具有矢量算术运算和复杂广播能力的快速且节省空间的多维数 ...
- Atlas快速入门
先说一些废话 之前的公司在数据中台的项目上调研决定启用了Atlas作为我们数据血缘管理的工具,让我给大家写了一份Atlas快速入门的文档,所以在这里我将这篇文档以一个纯新手视角的方式再一次优化,希望能 ...
- kettle从入门到精通 第十四课 kettle kafka 生产者和消费者
1.本节课讲解kafka生产者和消费者两个步骤.这两个组件可以实现数据实时同步(后续课程会讲解). 2.kafka producer 步骤 1)step name:自定义名称 2)connection ...
- Steam Epic 启动程序默认地址
Steam Epic 启动程(启动器)序默认地址 "D:\Games\EpicAPP\Epic Games\Launcher\Portal\Binaries\Win32\EpicGamesL ...
- Go版RuoYi
RuoYi-Go https://github.com/Kun-GitHub/RuoYi-Go 1. 关于我 个人介绍 2. 介绍 后端用Go写的RuoYi权限管理系统 (功能正在持续实现)后端 G ...
- radis简单学习笔记
原来写接口只用了本机缓存cache 来学习一下radis,用法应该跟cache一样吧,为了配套负载均衡的多服务器是多个服务器都可以读取缓存 一.下载 找了好长时间 github有的时候能上有的时候就上 ...
- 14-LNMP搭建
介绍 LNMP: Linux + Nginx + Mysql/Mariadb + PHP 借助LNMP,我们就能搭建一个动态的网页. 安装Nginx 详细nginx教程:https://blog.cs ...
- Windows下USB声卡音量调整
买了一个绿联的USB声卡, 但是默认的音量太大了,最低音量都响的不行. 查了一下, 发现了一个叫EqualizerAPO的软件可以调整输出设备的音量. https://equalizerapo.com ...
- 千万别忽视基础!十张图带你一步步理解Java内存结构!
作为一个Java程序员,在日常的开发中,不必像C/C++程序员那样,为每一个内存的分配而操心,JVM会替我们进行自动的内存分配和回收,方便我们开发.但是一旦发生内存泄漏或者内存溢出,如果对Java内存 ...