目录
一、slf4j+logback日志
1. 基本概念
日志级别
- FATAL:表示需要立即被处理的系统级错误。当该错误发生时,表示服务已经出现了某种程度的不可用,系统管理员需要立即介入。这属于最严重的日志级别,因此该日志级别必须慎用,如果这种级别的日志经常出现,则该日志也失去了意义。通常情况下,一个进程的生命周期中应该只记录一次FATAL级别的日志,即该进程遇到无法恢复的错误而退出时。当然,如果某个系统的子系统遇到了不可恢复的错误,那该子系统的调用方也可以记入FATAL级别日志,以便通过日志报警提醒系统管理员修复;
- ERROR:该级别的错误也需要马上被处理,但是紧急程度要低于 FATAL 级别。当ERROR错误发生时,已经影响了用户的正常访问。从该意义上来说,实际上ERROR错误和FATAL错误对用户的影响是相当的。FATAL相当于服务已经挂了,而ERROR相当于好死不如赖活着,然而活着却无法提供正常的服务,只能不断地打印ERROR日志。特别需要注意的是,ERROR和FATAL都属于服务器自己的异常,是需要马上得到人工介入并处理的。而对于用户自己操作不当,如请求参数错误等等,是绝对不应该记为ERROR日志的;
- WARN:该日志表示系统可能出现问题,也可能没有,这种情况如网络的波动等。对于那些目前还不是错误,然而不及时处理也会变为错误的情况,也可以记为WARN日志,例如一个存储系统的磁盘使用量超过阀值,或者系统中某个用户的存储配额快用完等等。对于WARN级别的日志,虽然不需要系统管理员马上处理,也是需要及时查看并处理的。因此此种级别的日志也不应太多,能不打WARN级别的日志,就尽量不要打;
- INFO:该种日志记录系统的正常运行状态,例如某个子系统的初始化,某个请求的成功执行等等。通过查看INFO级别的日志,可以很快地对系统中出现的 WARN,ERROR,FATAL错误进行定位。INFO日志不宜过多,通常情况下,INFO级别的日志应该不大于TRACE日志的10%;
- DEBUG or TRACE:这两种日志具体的规范应该由项目组自己定义,该级别日志的主要作用是对系统每一步的运行状态进行精确的记录。通过该种日志,可以查看某一个操作每一步的执行过程,可以准确定位是何种操作,何种参数,何种顺序导致了某种错误的发生。可以保证在不重现错误的情况下,也可以通过DEBUG(或TRACE)级别的日志对问题进行诊断。需要注意的是,DEBUG日志也需要规范日志格式,应该保证除了记录日志的开发人员自己外,其他的如运维,测试人员等也可以通过 DEBUG(或TRACE)日志来定位问题;
日志级别优先级:
ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF
logback中是没有fatal这个级别的,如果使用会映射成error输出。
2. 用法
SpringBoot 中默认使用 commons logging 门面(日志框架),也就是说已经引入了 commons logging 相关的依赖,我们可以选择(实现) jdk 自带的 java.util.logging 也可以选择log4j也可以选择logback。
SpringBoot 项目(使用了一个starter)中内置一个 logback 的实现,我们可以修改配置文件实现输出定制的日志格式。
设置resources下的文件logback.xml
示例:
<?xml version="1.0" encoding="UTF-8" ?>
<!-- logback中一共有5种有效级别,分别是TRACE、DEBUG、INFO、WARN、ERROR,优先级依次从低到高 -->
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<property name="DIR_NAME" value="spring-helloworld"/>
<!-- 将记录日志打印到控制台 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n</pattern>
</encoder>
</appender>
<!-- RollingFileAppender begin -->
<appender name="ALL" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 根据时间来制定滚动策略 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${user.dir}/logs/${DIR_NAME}/all.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<!-- 根据文件大小来制定滚动策略 -->
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>30MB</maxFileSize>
</triggeringPolicy>
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n</pattern>
</encoder>
</appender>
<appender name="ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 根据时间来制定滚动策略 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${user.dir}/logs/${DIR_NAME}/error.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<!-- 根据文件大小来制定滚动策略 -->
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>10MB</maxFileSize>
</triggeringPolicy>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n</pattern>
</encoder>
</appender>
<appender name="WARN" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 根据时间来制定滚动策略 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${user.dir}/logs/${DIR_NAME}/warn.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<!-- 根据文件大小来制定滚动策略 -->
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>10MB</maxFileSize>
</triggeringPolicy>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>WARN</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n</pattern>
</encoder>
</appender>
<appender name="INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 根据时间来制定滚动策略 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${user.dir}/logs/${DIR_NAME}/info.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<!-- 根据文件大小来制定滚动策略 -->
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>10MB</maxFileSize>
</triggeringPolicy>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n</pattern>
</encoder>
</appender>
<appendername="DEBUG"class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 根据时间来制定滚动策略 -->
<rollingPolicyclass="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${user.dir}/logs/${DIR_NAME}/debug.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<!-- 根据文件大小来制定滚动策略 -->
<triggeringPolicyclass="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>10MB</maxFileSize>
</triggeringPolicy>
<filterclass="ch.qos.logback.classic.filter.LevelFilter">
<level>DEBUG</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n</pattern>
</encoder>
</appender>
<appendername="TRACE"class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 根据时间来制定滚动策略 -->
<rollingPolicyclass="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${user.dir}/logs/${DIR_NAME}/trace.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<!-- 根据文件大小来制定滚动策略 -->
<triggeringPolicyclass="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>10MB</maxFileSize>
</triggeringPolicy>
<filterclass="ch.qos.logback.classic.filter.LevelFilter">
<level>TRACE</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n</pattern>
</encoder>
</appender>
<appendername="SPRING"class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 根据时间来制定滚动策略 -->
<rollingPolicyclass="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${user.dir}/logs/${DIR_NAME}/springframework.%d{yyyy-MM-dd}.log
</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<!-- 根据文件大小来制定滚动策略 -->
<triggeringPolicyclass="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>10MB</maxFileSize>
</triggeringPolicy>
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n</pattern>
</encoder>
</appender>
<!-- RollingFileAppender end -->
<!-- logger begin -->
<!-- 本项目的日志记录,分级打印 -->
<loggername="org.zp.notes.spring"level="TRACE"additivity="false">
<appender-refref="STDOUT"/>
<appender-refref="ERROR"/>
<appender-refref="WARN"/>
<appender-refref="INFO"/>
<appender-refref="DEBUG"/>
<appender-refref="TRACE"/>
</logger>
<!-- SPRING框架日志 -->
<loggername="org.springframework"level="WARN"additivity="false">
<appender-refref="SPRING"/>
</logger>
<rootlevel="TRACE">
<appender-refref="ALL"/>
</root>
<!-- logger end -->
</configuration>
自己常用的:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
<!-- 控制台输出 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%clr([%d{HH:mm:ss.SSS}]) %msg %clr([%level=>]) %C.%M\(%F:%L\)%n</pattern>
</encoder>
</appender>
<!-- 系统模块日志级别控制 -->
<logger name="com.ruoyi" level="info" />
<!-- Spring日志级别控制 -->
<logger name="org.springframework" level="warn" />
<root level="info">
<appender-ref ref="console" />
</root>
</configuration>
调用
- 使用idea在pom文件加入lombok的依赖
- 类上面添加@Sl4j注解,然后使用log打印日志;
二、navicat
1. 建表
- 直接建表
- 向导建表
可以使用向导功能将excel表转为数据库表,不过excel表要符合格式。
- sql文件建表
将原有的表转成sql文件,将sql文件放在别的环境中执行就可以得到。
2. 索引
设计表中可以设置字段索引。
3. 查找
当数据量大时使用sql查询语句会比较慢,那么直接在navicat中使用筛选功能。
可以对查询出来的数据进行处理(导出成excel表)。
三、Linux常用指令
1. 复制粘贴
Linux 系统下的复制粘贴不是 ctrl + C/V 而是 ctrl + insert(复制),shift + insert(粘贴)。
2. 常用指令
系统指令:
ifconfig 查看IP地址。
top 运行的进程和系统性能信息。
free -h 内存使用情况。
df -h 磁盘使用情况。
systemctl status firewalld 防火墙状态。
systemctl start firewalld 开启防火墙。
systemctl stop firewalld 中断防火墙。
文本编辑:
vim test.txt
vi和vim,是 linux 中的文本编辑器,用来在linux中创建、查看或者编辑文本文件。
编辑模式:在一般模式下,按 i 键或者 a 键,进入编辑模式;
可以编辑文件内容;但是不能保存编辑的内容;按 esc 键,可以回到一般模式。
命令行模式:在一般模式下,按 shift + :进入命令行模式;
输入:
wq 保存并退出编辑器
q 只是退出编辑器
q! 不保存强制退出编辑器
文件和目录相关操作:
切换目录
ll 查看当前目录下的所有文件。
cd .. 切换到上级目录。
cd /opt 切换到opt目录下。
cd bin 切换到当前目录的bin目录下。
cd test 切换到当前目录下的test目录下。
创建目录
mkdir /opt/data 在opt目录下创建目录data。
mkdir -p data/mysql
在当前目录下创建目录data,并且在data下创建mysql(一次创建多级目录)。
复制文件
cp gateway.jar gateway-bank-2023-08-12.jar
把文件gateway.jar复制为gateway-bank-2023-08-12.jar作为备份。
删除文件
rm test.txt 提示删除 text.txt 文件。
rm -f test.txt 强制删除 text.txt 文件。
rm -r data 提示递归删除data目录。
rm -rf data 强制递归删除data目录。
查看文件
cat text.txt 查看text.txt文件内容,所有内容一次性显示出来
tail -f text.txt 实时查看text.txt文件的末尾内容
grep "mysql" text.txt //查看文件text.txt中包含mysql的内容
查找文件
find / -name text.txt 从根目录查找名称为text.txt的文件
find /opt *.java 查找opt目录下的.java文件
find /opt *.java | grep user
搜索opt目录下,所有名称包含user的.java文件
四、idea的使用
1. 导入项目
导入项目时需要修改maven的构建,私仓地址和本地仓库地址;还有启动的脚本配置。
修改maven:
启动脚本:
2. 快捷键
ctrl + alt + l 格式化
alt + enter 补全创建变量
new Arrlist<>();
3. debug模式
- 打断点会造成服务一直启动不起来需要将断点全部取消掉再启动;
- debug模式可以添加变量到控制台可以监控其变化;
- 打断点时可以设置断点精确到循环的条件时停下来(i=2);
五、Git
- 执行克隆命令和初始化仓库命令
知识星球 | 深度连接铁杆粉丝,运营高品质社群,知识变现的工具
1. 创建本地仓库
在项目文件夹初始化仓库
git init
2. 新建远程仓库
3. 生成SSH公钥
- 生成SSH公钥
-
- ssh-keygen -t rsa
- 不断回车
-
-
- 如果公钥已经存在,则自动覆盖
-
- Gitee设置账户共公钥
-
- 获取公钥
-
-
- cat ~/.ssh/id_rsa.pub
-
本地仓库连接远程仓库
命令: git remote add <远端名称> <仓库路径SSH>
- 远端名称,默认是origin,取决于远端服务器设置
- 仓库路径,从远端服务器获取此SSH,
4. 在Idea中配置Git(不用命令行)
创建本地仓库
设置远程仓库
提交并推送到本地仓库
5. 版本回退和撤销修改
- 已提交,没有push
1)git reset --soft 撤销commit
2)git reset --mixed 撤销commit和add两个动作
- 已提交,并且push
1)git reset --hard 撤销并舍弃版本号之后的提交记录。使用需要谨慎
2)git revert 撤销。但是保留了提交记录
6. 解决冲突和补丁操作
提交冲突的原因:提交者的版本库<远程库
冲突情况一:能自动合并的冲突
文件中有两个函数,分别由两个人负责开发维护,需要将另外一个人修改的代码合并在一起 (冲突为不同角色对同一内容的修改)
冲突情况二:手动解决冲突
如何实现本地同步:git pull(先拉下来,解决冲突再提交)
六、解决BUG
1. bug一般的处理流程
2. 错误复现
- F12打开控制台
复制地址到postman里
- 操作postman
将从控制台得到的body,headers,鉴权token(key-value)等请求参数填写到postman中
- 操作idea
复制从控制台得到的url的后面几位复制下来到idea中查找
七、Redis
1. 新项目的使用
- 配置 redis 信息(主要就是 redis 的地址、端口、密码)
redis:
host: 192.168.64.100
port: 6379
timeout: 1000
jedis:
pool:
max-active: 8
max-wait: 1000
max-idle: 500
min-idle: 0
- 引入 redisTemplate 依赖
- 写 redis 的工具类(封装好 redis 再使用)
Redis 配置类:
@Configuration
public class RedisConfiguration {
@Bean
public StringRedisTemplate getStringRedisTemplate(RedisConnectionFactory factory) {
StringRedisTemplate redisTemplate = new StringRedisTemplate();
redisTemplate.setConnectionFactory(factory);
return redisTemplate;
}
@Bean(name = "redisSessionTemplate")
public RedisTemplate<String, SessionContext> getredisSessionTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, SessionContext> redisTemplate = new RedisTemplate<String, SessionContext>();
redisTemplate.setConnectionFactory(factory);
redisTemplate.setKeySerializer(new StringRedisSerializer()); // key的序列化类型
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); // value的序列化类型
return redisTemplate;
}
@Bean
public RedisSessionHelper redisSessionHelper() {
return new RedisSessionHelper();
}
@Bean
public RedisHelper redisHelper() {
return new RedisHelper();
}
}
Redis 方法:
public class RedisHelper {
@Autowired
private RedisTemplate<String, String> redisTemplate;
public void setObject(String prefix, String key, Object value) {
String redisKey = getRedisKey(prefix, key);
String serializeData = JSON.toJSONString(value);
redisTemplate.opsForValue().set(redisKey, serializeData);
}
public <T> T getObject(String prefix, String key, Class<T> clazz) {
String redisKey = getRedisKey(prefix, key);
String serializeData = redisTemplate.opsForValue().get(redisKey);
return JSON.parseObject(serializeData, clazz);
}
public void delKey(String prefix, String key) {
String redisKey = getRedisKey(prefix, key);
redisTemplate.delete(redisKey);
}
public boolean exists(String prefix, String key) {
String redisKey = getRedisKey(prefix, key);
return redisTemplate.hasKey(redisKey);
}
public void addGEO(String prefix, String key, String lng, String lat, String tripId) {
String redisKey = getRedisKey(prefix, key);
Point point = new Point(Float.parseFloat(lng), Float.parseFloat(lat));
redisTemplate.opsForGeo().add(redisKey, point, tripId);
}
public void delGEO(String prefix, String key, String tripId) {
String redisKey = getRedisKey(prefix, key);
redisTemplate.opsForGeo().remove(redisKey, tripId);
}
/**
* 计算两点之间距离
*
* @param prefix
* @param key
* @param startLocation
* @param endLocation
* @return
*/
public float geoDistance(String prefix, String key, String startLocation, String endLocation) {
String redisKey = getRedisKey(prefix, key);
Distance distance = redisTemplate.opsForGeo()
.distance(redisKey, startLocation, endLocation, RedisGeoCommands.DistanceUnit.KILOMETERS);//params: key, 地方名称1, 地方名称2, 距离单位
return (float) distance.getNormalizedValue();
}
public Map<String, GeoBO> geoNearByXY(String prefix, String key, float lng, float lat) {
String redisKey = getRedisKey(prefix, key);
//以当前坐标为中心画圆
Circle circle = new Circle(
new Point(lng, lat),
new Distance(HtichConstants.STROKE_DIAMETER_RANGE, Metrics.KILOMETERS)
);
//限制20条,可以根据实际情况调整
RedisGeoCommands.GeoRadiusCommandArgs args =
RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs()
.includeDistance().includeCoordinates()
.sortAscending().limit(20);
//查找这个范围内的行程点
GeoResults<RedisGeoCommands.GeoLocation<String>> results = redisTemplate.opsForGeo()
.radius(redisKey, circle, args);
return geoResultPack(results);
}
/**
* 根据地址名词进行搜索
*
* @param prefix 前缀
* @param key key
* @param location 地址:行程ID+role
* @param isStart 是否时起始行程
* @return
*/
public Map<String, GeoBO> geoNearByPlace(String prefix, String key, String location, boolean isStart) {
String redisKey = getRedisKey(prefix, key);
Distance distance = new Distance(HtichConstants.STROKE_DIAMETER_RANGE, Metrics.KILOMETERS);//params: 距离量, 距离单位
RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeDistance().includeCoordinates().sortAscending().limit(20);
GeoResults<RedisGeoCommands.GeoLocation<String>> results = redisTemplate.opsForGeo()
.radius(redisKey, location, distance, args);//params: key, 地方名称, Circle, GeoRadiusCommandArgs
return geoResultPack(results);
}
public void addHash(String prefix, String key, String hkey, String value) {
String redisKey = getRedisKey(prefix, key);
redisTemplate.opsForHash().put(redisKey, hkey, value);
}
public void delHash(String prefix, String key, String... hkeys) {
String redisKey = getRedisKey(prefix, key);
redisTemplate.opsForHash().delete(redisKey, hkeys);
}
public String getHash(String prefix, String key, String hkey) {
String redisKey = getRedisKey(prefix, key);
HashOperations<String, String, String> hashOperations = redisTemplate.opsForHash();
return hashOperations.get(redisKey, hkey);
}
public Map<String, String> getHashByMap(String prefix, String key) {
String redisKey = getRedisKey(prefix, key);
HashOperations<String, String, String> hashOperations = redisTemplate.opsForHash();
return hashOperations.entries(redisKey);
}
public void addZset(String prefix, String key, String value, float score) {
String redisKey = getRedisKey(prefix, key);
redisTemplate.opsForZSet().add(redisKey, value, score);
}
public List<ZsetResultBO> getZsetSortVaues(String prefix, String key) {
String redisKey = getRedisKey(prefix, key);
List<ZsetResultBO> zsetResultBOList = new ArrayList<>();
//rangeByScoreWithScores(K,Smin,Smax,[offset],[count])
Set<ZSetOperations.TypedTuple<String>> typedTuples = redisTemplate.opsForZSet().reverseRangeByScoreWithScores(redisKey, 0, 100,0,20);
for (ZSetOperations.TypedTuple<String> typedTuple : typedTuples) {
zsetResultBOList.add(new ZsetResultBO(typedTuple.getScore().floatValue(), typedTuple.getValue()));
}
return zsetResultBOList;
}
public float getZsetScore(String prefix, String key, String value) {
String redisKey = getRedisKey(prefix, key);
return redisTemplate.opsForZSet().score(redisKey, value).floatValue();
}
/**
* 删除zset数据
*
* @param prefix
* @param key
* @param value
*/
public void delZsetByKey(String prefix, String key, String value) {
String redisKey = getRedisKey(prefix, key);
redisTemplate.opsForZSet().remove(redisKey, value);
}
private String getRedisKey(String prefix, String key) {
return prefix + key;
}
/**
* GEO结果集包装
*
* @param results
* @return
*/
private Map<String, GeoBO> geoResultPack(GeoResults<RedisGeoCommands.GeoLocation<String>> results) {
Map<String, GeoBO> geoBOMap = new HashMap();
for (GeoResult<RedisGeoCommands.GeoLocation<String>> result : results) {
RedisGeoCommands.GeoLocation<String> content = result.getContent();
String name = content.getName();
// 距离中心点的距离
Distance dis = result.getDistance();
Point pos = content.getPoint();
geoBOMap.put(name, new GeoBO(name, ((float) dis.getValue()), String.valueOf(pos.getX()), String.valueOf(pos.getY())));
}
return geoBOMap;
}
}
- 使用 redis 的工具类
RedisHelper.setObject(prefix,key);
2. 项目已有 Redis
- 全局搜索 redis
查看别人如何使用 redis (查看它封装的 redis)。
3. Redis 的使用场景
- 有些图表数据来源的表数据较多,处理过程复杂,导致接口响应慢,不符合客户需求
- 自定义注解,需要把校验信息存储到 redis 中
- 一些经常使用的数据需要在项目启动的时候就从数据库里加载到 redis 中
- 验证码、token信息需要加载到redis中
至于缓存一致性,直接先更新,再删除就能满足大部分的场景
八、本地跑项目
1. 有开发环境
直接使用公司的开发环境(公司有一台服务器去部署项目),不需要本地安装项目启动所需的开发环境,比如jdk、MySQL、redis等(不用担心这些的版本问题)。
• 远程配置文件(启动脚本)
将其添加在
• 本地配置文件
application.yml下
--spring.profiles.active-dev
2. 无开发环境
需要本地安装项目启动所需要的开发环境,需要安装的东西可以从项目的配置文件里面去看(保证不会出现版本问题)。
3. 设置maven
配置 maven
九、如何写一个接口
1. 模糊分页查询
SpringBoot实现分页模糊查询_spring boot 模糊查询-CSDN博客
PageHelper设置pageSize失效问题解决_使用新版本pagehelper pagehelper.startpage失效-CSDN博客
Controller层:
@RestController
@RequestMapping("/camera")
public class CameraAlarmController{
@Autowired
private WarehouseOutService warehouseOutService;
/**
* 获取设备相关信息
* @param equipAttribute
* @param equipType
* @param keywords
* @param pageNum
* @param pageSize
* @return
*/
@GetMapping("/getOutboundEquipmentInfo")
public Result getOutboundEquipmentInfo(@RequestParam(value="operationType",required=false) Integer operationType,
@RequestParam(value="warehouseId",required=false Integer warehouseId,
@RequestParam(value="equipAttribute",required=false) String equipAttribute,
@RequestParam(value="equipTypeId",required=false) Integer equipTypeId,
@RequestParam(value="keywords",required=false) String keywords,
@RequestParam(value="pageNum",required=false,defaultValue="1") Integer pageNum,
@RequestParam(value="pageSize",required=false,defaultValue="10") Integer pageSize){
PageHelper.startPage(pageNum,pageSize);
PageInfo<OutboundEquipmentInfo> pageInfo=new PageInfo<>(warehouseOutService.getOutboundEquipmentInfo(equipAttribute,
equipTypeId.warehouseId,operationType,keywords));
return new SuccessResult(pageInfo);
}
}
Service层:
定义service层接口
public interface IWarehouseOutService{
List<OutboundEquipmentInfoVo> getOutboundEquipmentInfo(String equipAttribute,
Integer equipTypeId,Integer warehouseId,Integer operationType,String keywords);
}
service层实现
@Service
@Slf4j
public class WarehouseServiceImpl implements IWarehouseOutService{
@Autowired
private WmEquipInfoMapper wmEquipInfoMapper;
@Override
public List<OutboundEquipmentInfoVo> getOutboundEquipmentInfo(String equipAttribute,
Integer equipTypeId,
Integer warehouseId,
Integer operationType,
String keywords) {
//入库操作
if (operationType==WmConstant.EQUIPMENT_INFO_INCOME){
List<OutboundEquipmentInfoVo> outList=wmEquipInfoMapper.getEquipmentInformation(equipAttribute,equipTypeId,keywords);
List<WmRepertoryInfoDto> allEquip = wmRepertoryInfoMapper.getAllEquip(warehouseId);
outList.forEach(info->{
Optional<WmRepertoryInfoDto> wmRepertoryInfoDto = allEquip.stream().filter(all -> all.getEquipId().equals(info.getId())).findFirst();
if (wmRepertoryInfoDto.isPresent()){
info.setInventoryQuantity(wmRepertoryInfoDto.get().getNum());
info.setDamagesNum(wmRepertoryInfoDto.get().getDamagesNum());
}else {
info.setInventoryQuantity(0);
info.setDamagesNum(0);
}
});
return outList;
}else{
//出库操作
List<WmRelationEquipDto> relationEquipDtos=wmOutWarehouseMapper.getOutboundApproval(warehouseId);
List<OutboundEquipmentInfoVo> outboundEquipmentInfo = wmEquipInfoMapper.getOutboundEquipmentInfo(equipAttribute, warehouseId, equipTypeId, keywords);
outboundEquipmentInfo.forEach(out-> relationEquipDtos.forEach(re->{
if (Integer.valueOf(re.getEquipId().toString()).equals(out.getId())){
Integer num=out.getInventoryQuantity()-re.getNum();
out.setInventoryQuantity(num<0?0:num);
}
})
);
return outboundEquipmentInfo;
}
}
mapper层:
2. 批量删除
3. 新增
十、对Stream的使用
1. 对map集合的操作
• map集合value总和
long sum = datas.entrySet().stream().filter(entry -> !entry.getKey().equals("1") && !entry.getKey().equals("3")
&& !entry.getKey().equals("6") && !entry.getKey().equals("7") && !entry.getKey().equals("8")
&& !entry.getKey().equals("10"))
.mapToLong(entry -> Long.parseLong(entry.getValue())).sum();
- 合并两个map,key相同的value进行加和、
map1.forEach((key, value) -> map2.merge(key, value, Integer::sum));
//map1合并到map2,map类型<String,Integer>
2. 对list集合的操作
- 根据时间对list集合的对象进行升序排序
List<ThirdCloudAlarmDto> dtoList = collect.get(key).stream()
.sorted(Comparator.comparing(ThirdCloudAlarmDto::getOccurTime)).collect(Collectors.toList());
-
- stream流根据时间筛选所需数据
List<CameraAlarmDto> collect = cameraAlarms.stream().
filter(e -> (e.getTimeLast().after(startDate) &&
e.getTimeLast().before(endDate))).collect(Collectors.toList());
-
- 对list集合根据某一个元素进行过滤,并选取过滤后的集合对象中的某些属性组合成新的集合
groups.forEach(group -> {
List<DceEquipGroupInfoVo> dceEquipGroupInfoVos = dceEquipInfoDtos.stream().
filter((DceEquipGroupInfoDto d) -> group.getNodeName().equals(d.getNodeName())).
map(item -> {
DceEquipGroupInfoVo equipGroupInfoVo = new DceEquipGroupInfoVo();
equipGroupInfoVo.setEquipKey(item.getEquipKey()).setEquipName(item.getEquipName());
return equipGroupInfoVo;
}).distinct().collect(Collectors.toList());
}
-
- 对List集合使用分页查询
thirdCloudHostDtos = thirdCloudHostDtos.stream().skip((pageNum - 1) * pageSize)
.limit(pageSize).collect(Collectors.toList());
-
- 对list集合中对象中属性为字符串数字比较倒叙排序
List<MetricVO> listTop5 = list.stream().sorted(Comparator.comparing(MetricVO::getValue,
Comparator.comparingInt(Integer::parseInt)).reversed()).limit(5).collect(Collectors.toList());
- 使用stream流进行模糊查询
keyWordsList = dtoList.stream().filter(e -> Boolean.FALSE ? e.getServerIp().equals(keyWords)
|| e.getServerPort().equals(keyWords) || e.getClientIp().equals(keyWords)
|| e.getClientPort().equals(keyWords) || e.getAppProgram().equals(keyWords)
|| e.getProtocol().equals(keyWords) :
(e.getServerIp().contains(keyWords)) || e.getServerPort().contains(keyWords) || e.getClientIp().contains(keyWords)
|| e.getClientPort().contains(keyWords) || e.getAppProgram().contains(keyWords)
|| e.getProtocol().contains(keyWords)).collect(Collectors.toList());
- 取多个list集合的交集
dealedList = keyWordsList.stream().filter(appList::contains).filter(protocolList::contains).collect(Collectors.toList());
- 根据对象某一字段组成类型集合
List<String> appTypes = dtoList.stream().filter(item ->item.getAppProgram() != null)
.map(HistoricalFlowDto::getAppProgram).distinct().collect(Collectors.toList());
- 求list属性中某一属性的和
Integer totalOrders = usedStatisticsVoList.stream().mapToInt(ServiceUsedStatisticsVo :: getWorkOrderNum).sum(); //list中存储的是对象
Long totalOrders = mapList.stream().mapToLong((s) -> Long.valueOf(String.valueOf(s.get("num")))).sum(); //list中存储的是map集合
- 根据list中对象的某一属性组合成新的集合
List instanceIds = instances.stream().map(ThirdCloudInstance::getInstanceId).collect(Collectors.toList());
- 按照list对象中的某一属性进行分组
Map<String, List> collect = list.stream().collect(Collectors.groupingBy(MonitorBusyDegreeResult::getDataDesc));
- list中存储对象,根据对象某一属性排序
List< MetricVO> metricVosBusy = new ArrayList<>();
businessSystems.stream().sorted(Comparator.comparing(MonitorBusyDegreeResult::getBusyDegree, Comparator.reverseOrder())).collect(Collectors.toList())
.forEach(e -> {
metricVosBusy.add(new MetricVO().setKey(e.getDataDesc()).setValue(String.valueOf(e.getBusyDegree())));
});
- list中存储map,按map中的某一key对应的value进行排序,取排行
//正序//
List<Map<String, Object>> listTop9 = list.stream().sorted(Comparator.comparing(e -> org.apache.commons.collections.MapUtils.getLong(e, "count"))).limit(9).collect(Collectors.toList());
//逆序
List<Map<String, Object>> listTop9 = list.stream().sorted((c1, c2) -> org.apache.commons.collections.MapUtils.getDouble(c2, "count") .compareTo(org.apache.commons.collections.MapUtils.getDouble(c1, "count"))).limit(9).collect(Collectors.toList());
十一、RabbitMQ
1. 引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
2. 配置连接信息
• 生产者配置
rabbitmq:
host: 192.168.64.100
port: 5672
username: guest
password: guest
virtual-host: /
• 消费者配置
rabbitmq:
host: 192.168.64.100
port: 5672
username: guest
password: guest
virtual-host: /
publisher-returns: true
listener:
simple:
acknowledge-mode: manual
3. 设置消息转换器(可以省略)
目的:默认情况下 Spring 采用的序列化方式是 JDK 序列化,而 JDK 的序列化存在可读性性差、占用内存大、存在安全漏洞等问题。所以,这里我们一般便用 Jackson 的序列化代替JDK的序列化。
在生产者和清费者的启动类上加上如下代码:
4. 配置文件
@Configuration
public class RabbitConfig {
/**
* 延迟时间 单位毫秒
*/
private static final long DELAY_TIME = 1000 * 30;
//行程超时队列
public static final String STROKE_OVER_QUEUE = "STROKE_OVER_QUEUE";
//行程死信队列
public static final String STROKE_DEAD_QUEUE = "STROKE_DEAD_QUEUE";
//行程超时队列交换器
public static final String STROKE_OVER_QUEUE_EXCHANGE = "STROKE_OVER_QUEUE_EXCHANGE";
//行程死信队列交换器
public static final String STROKE_DEAD_QUEUE_EXCHANGE = "STROKE_DEAD_QUEUE_EXCHANGE";
//行程超时交换器 ROUTINGKEY
public static final String STROKE_OVER_KEY = "STROKE_OVER_KEY";
//行程死信交换器 ROUTINGKEY
public static final String STROKE_DEAD_KEY = "STROKE_DEAD_KEY";
/**
* 声明行程超时队列
*
* @return
*/
@Bean
public Queue strokeOverQueue() {
//【重要配置】超时队列配置,死信队列的绑定在该方法中实现
//需要用到以下属性:
Map<String,Object> args = new HashMap<>();
// x-dead-letter-exchange 这里声明当前队列绑定的死信交换机
args.put("x-dead-letter-exchange",STROKE_DEAD_QUEUE_EXCHANGE);
// x-dead-letter-routing-key 这里声明当前队列的死信路由key
args.put("x-dead-letter-routing-key",STROKE_DEAD_KEY);
// x-message-ttl 声明队列的TTL
args.put("x-message-ttl",DELAY_TIME);
return new Queue(STROKE_OVER_QUEUE,true,false,false,args);
}
/**
* 声明行程死信队列
*
* @return
*/
@Bean
public Queue strokeDeadQueue() {
return new Queue(STROKE_DEAD_QUEUE,true);
}
/**
* 创建行程超时队列交换器
*
* @return
*/
@Bean
DirectExchange strokeOverQueueExchange() {
return new DirectExchange(STROKE_OVER_QUEUE_EXCHANGE);
}
/**
* 创建行程死信队列交换器
*
* @return
*/
@Bean
DirectExchange strokeDeadQueueExchange() {
return new DirectExchange(STROKE_DEAD_QUEUE_EXCHANGE);
}
/**
* 行程超时队列绑定
*
* @return
*/
@Bean
Binding bindingStrokeOverDirect() {
return BindingBuilder.bind(strokeOverQueue()).to(strokeOverQueueExchange()).with(STROKE_OVER_KEY);
}
/**
* 行程死信队列绑定
*
* @return
*/
@Bean
Binding bindingStrokeDeadDirect() {
return BindingBuilder.bind(strokeDeadQueue()).to(strokeDeadQueueExchange()).with(STROKE_DEAD_KEY);
}
}
5. 生产者代码
@Component
public class MQProducer {
private final static Logger logger = LoggerFactory.getLogger(MQProducer.class);
@Autowired
RabbitTemplate rabbitTemplate;
/**
* 发送延时订单MQ
*
* @param strokeVO
*/
public void sendOver(StrokeVO strokeVO) {
String mqMessage = JSON.toJSONString(strokeVO);
logger.info("send timeout msg:{}",mqMessage);
// TODO:任务4.2-发送邀请消息
rabbitTemplate.convertAndSend(RabbitConfig.STROKE_OVER_QUEUE_EXCHANGE,RabbitConfig.STROKE_OVER_KEY,mqMessage);
}
}
6. 消费者代码
@Component
public class MQConsumer{
private final static Logger logger = LoggerFactory.getLogger(MQConsumer.class);
@Autowired
private StrokeHandler strokeHandler;
/**
* 行程超时监听
*
* @param massage
* @param channel
* @param tag
*/
@RabbitListener(
bindings =
{
@QueueBinding(value = @Queue(value = RabbitConfig.STROKE_DEAD_QUEUE, durable = "true"),
exchange = @Exchange(value = RabbitConfig.STROKE_DEAD_QUEUE_EXCHANGE), key = RabbitConfig.STROKE_DEAD_KEY)
})
@RabbitHandler
public void processStroke(Message massage, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long tag) {
// TODO:任务4.3-接收死信队列消息(以下代码已完成,用于验证配置和发送无误)
StrokeVO strokeVO = JSON.parseObject(massage.getBody(), StrokeVO.class);
logger.info("get dead msg:{}",massage.getBody());
if (null == strokeVO) {
return;
}
try {
strokeHandler.timeoutHandel(strokeVO);
//手动确认机制
channel.basicAck(tag, false);
} catch (Exception e) {
e.printStackTrace();
}
}
}