Commit 43c6122d80a91adbaa55209c61808d5f3ff755f1

Authored by panlinlin
1 parent a333e5f2

添加完整接口

... ... @@ -5,17 +5,32 @@
5 5 <parent>
6 6 <groupId>org.springframework.boot</groupId>
7 7 <artifactId>spring-boot-starter-parent</artifactId>
8   - <version>2.4.5</version>
9   - <relativePath/> <!-- lookup parent from repository -->
  8 + <version>2.3.5.RELEASE</version>
10 9 </parent>
11 10 <groupId>top.panll.assist</groupId>
12 11 <artifactId>wvp-pro-assist</artifactId>
13 12 <version>2.0.0</version>
14 13 <name>wvp-pro-assist</name>
15   - <description>Demo project for Spring Boot</description>
  14 + <description></description>
16 15 <properties>
17 16 <java.version>1.8</java.version>
18 17 </properties>
  18 +
  19 + <repositories>
  20 + <repository>
  21 + <id>nexus-aliyun</id>
  22 + <name>Nexus aliyun</name>
  23 + <url>https://maven.aliyun.com/repository/public</url>
  24 + <layout>default</layout>
  25 + <snapshots>
  26 + <enabled>false</enabled>
  27 + </snapshots>
  28 + <releases>
  29 + <enabled>true</enabled>
  30 + </releases>
  31 + </repository>
  32 + </repositories>
  33 +
19 34 <dependencies>
20 35 <dependency>
21 36 <groupId>org.springframework.boot</groupId>
... ...
src/main/java/top/panll/assist/config/RedisUtil.java 0 → 100644
  1 +package top.panll.assist.config;
  2 +
  3 +import org.springframework.beans.factory.annotation.Autowired;
  4 +import org.springframework.data.redis.core.*;
  5 +import org.springframework.stereotype.Component;
  6 +import org.springframework.util.CollectionUtils;
  7 +
  8 +import java.util.*;
  9 +import java.util.concurrent.TimeUnit;
  10 +
  11 +/**
  12 + * @Description:Redis工具类
  13 + * @author: swwheihei
  14 + * @date: 2020年5月6日 下午8:27:29
  15 + */
  16 +@Component
  17 +@SuppressWarnings(value = {"rawtypes", "unchecked"})
  18 +public class RedisUtil {
  19 +
  20 + @Autowired
  21 + private RedisTemplate redisTemplate;
  22 +
  23 + /**
  24 + * 指定缓存失效时间
  25 + * @param key 键
  26 + * @param time 时间(秒)
  27 + * @return true / false
  28 + */
  29 + public boolean expire(String key, long time) {
  30 + try {
  31 + if (time > 0) {
  32 + redisTemplate.expire(key, time, TimeUnit.SECONDS);
  33 + }
  34 + return true;
  35 + } catch (Exception e) {
  36 + e.printStackTrace();
  37 + return false;
  38 + }
  39 + }
  40 +
  41 + /**
  42 + * 根据 key 获取过期时间
  43 + * @param key 键
  44 + * @return
  45 + */
  46 + public long getExpire(String key) {
  47 + return redisTemplate.getExpire(key, TimeUnit.SECONDS);
  48 + }
  49 +
  50 + /**
  51 + * 判断 key 是否存在
  52 + * @param key 键
  53 + * @return true / false
  54 + */
  55 + public boolean hasKey(String key) {
  56 + try {
  57 + return redisTemplate.hasKey(key);
  58 + } catch (Exception e) {
  59 + e.printStackTrace();
  60 + return false;
  61 + }
  62 + }
  63 +
  64 + /**
  65 + * 删除缓存
  66 + * @SuppressWarnings("unchecked") 忽略类型转换警告
  67 + * @param key 键(一个或者多个)
  68 + */
  69 + public boolean del(String... key) {
  70 + try {
  71 + if (key != null && key.length > 0) {
  72 + if (key.length == 1) {
  73 + redisTemplate.delete(key[0]);
  74 + } else {
  75 +// 传入一个 Collection<String> 集合
  76 + redisTemplate.delete(CollectionUtils.arrayToList(key));
  77 + }
  78 + }
  79 + return true;
  80 + } catch (Exception e) {
  81 + e.printStackTrace();
  82 + return false;
  83 + }
  84 + }
  85 +
  86 +// ============================== String ==============================
  87 +
  88 + /**
  89 + * 普通缓存获取
  90 + * @param key 键
  91 + * @return 值
  92 + */
  93 + public Object get(String key) {
  94 + return key == null ? null : redisTemplate.opsForValue().get(key);
  95 + }
  96 +
  97 + /**
  98 + * 普通缓存放入
  99 + * @param key 键
  100 + * @param value 值
  101 + * @return true / false
  102 + */
  103 + public boolean set(String key, Object value) {
  104 + try {
  105 + redisTemplate.opsForValue().set(key, value);
  106 + return true;
  107 + } catch (Exception e) {
  108 + e.printStackTrace();
  109 + return false;
  110 + }
  111 + }
  112 +
  113 + /**
  114 + * 普通缓存放入并设置时间
  115 + * @param key 键
  116 + * @param value 值
  117 + * @param time 时间(秒),如果 time < 0 则设置无限时间
  118 + * @return true / false
  119 + */
  120 + public boolean set(String key, Object value, long time) {
  121 + try {
  122 + if (time > 0) {
  123 + redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
  124 + } else {
  125 + set(key, value);
  126 + }
  127 + return true;
  128 + } catch (Exception e) {
  129 + e.printStackTrace();
  130 + return false;
  131 + }
  132 + }
  133 +
  134 + /**
  135 + * 递增
  136 + * @param key 键
  137 + * @param delta 递增大小
  138 + * @return
  139 + */
  140 + public long incr(String key, long delta) {
  141 + if (delta < 0) {
  142 + throw new RuntimeException("递增因子必须大于 0");
  143 + }
  144 + return redisTemplate.opsForValue().increment(key, delta);
  145 + }
  146 +
  147 + /**
  148 + * 递减
  149 + * @param key 键
  150 + * @param delta 递减大小
  151 + * @return
  152 + */
  153 + public long decr(String key, long delta) {
  154 + if (delta < 0) {
  155 + throw new RuntimeException("递减因子必须大于 0");
  156 + }
  157 + return redisTemplate.opsForValue().increment(key, delta);
  158 + }
  159 +
  160 +// ============================== Map ==============================
  161 +
  162 + /**
  163 + * HashGet
  164 + * @param key 键(no null)
  165 + * @param item 项(no null)
  166 + * @return 值
  167 + */
  168 + public Object hget(String key, String item) {
  169 + return redisTemplate.opsForHash().get(key, item);
  170 + }
  171 +
  172 + /**
  173 + * 获取 key 对应的 map
  174 + * @param key 键(no null)
  175 + * @return 对应的多个键值
  176 + */
  177 + public Map<Object, Object> hmget(String key) {
  178 + return redisTemplate.opsForHash().entries(key);
  179 + }
  180 +
  181 + /**
  182 + * HashSet
  183 + * @param key 键
  184 + * @param map 值
  185 + * @return true / false
  186 + */
  187 + public boolean hmset(String key, Map<Object, Object> map) {
  188 + try {
  189 + redisTemplate.opsForHash().putAll(key, map);
  190 + return true;
  191 + } catch (Exception e) {
  192 + e.printStackTrace();
  193 + return false;
  194 + }
  195 + }
  196 +
  197 + /**
  198 + * HashSet 并设置时间
  199 + * @param key 键
  200 + * @param map 值
  201 + * @param time 时间
  202 + * @return true / false
  203 + */
  204 + public boolean hmset(String key, Map<Object, Object> map, long time) {
  205 + try {
  206 + redisTemplate.opsForHash().putAll(key, map);
  207 + if (time > 0) {
  208 + expire(key, time);
  209 + }
  210 + return true;
  211 + } catch (Exception e) {
  212 + e.printStackTrace();
  213 + return false;
  214 + }
  215 + }
  216 +
  217 + /**
  218 + * 向一张 Hash表 中放入数据,如不存在则创建
  219 + * @param key 键
  220 + * @param item 项
  221 + * @param value 值
  222 + * @return true / false
  223 + */
  224 + public boolean hset(String key, String item, Object value) {
  225 + try {
  226 + redisTemplate.opsForHash().put(key, item, value);
  227 + return true;
  228 + } catch (Exception e) {
  229 + e.printStackTrace();
  230 + return false;
  231 + }
  232 + }
  233 +
  234 + /**
  235 + * 向一张 Hash表 中放入数据,并设置时间,如不存在则创建
  236 + * @param key 键
  237 + * @param item 项
  238 + * @param value 值
  239 + * @param time 时间(如果原来的 Hash表 设置了时间,这里会覆盖)
  240 + * @return true / false
  241 + */
  242 + public boolean hset(String key, String item, Object value, long time) {
  243 + try {
  244 + redisTemplate.opsForHash().put(key, item, value);
  245 + if (time > 0) {
  246 + expire(key, time);
  247 + }
  248 + return true;
  249 + } catch (Exception e) {
  250 + e.printStackTrace();
  251 + return false;
  252 + }
  253 + }
  254 +
  255 + /**
  256 + * 删除 Hash表 中的值
  257 + * @param key 键
  258 + * @param item 项(可以多个,no null)
  259 + */
  260 + public void hdel(String key, Object... item) {
  261 + redisTemplate.opsForHash().delete(key, item);
  262 + }
  263 +
  264 + /**
  265 + * 判断 Hash表 中是否有该键的值
  266 + * @param key 键(no null)
  267 + * @param item 值(no null)
  268 + * @return true / false
  269 + */
  270 + public boolean hHasKey(String key, String item) {
  271 + return redisTemplate.opsForHash().hasKey(key, item);
  272 + }
  273 +
  274 + /**
  275 + * Hash递增,如果不存在则创建一个,并把新增的值返回
  276 + * @param key 键
  277 + * @param item 项
  278 + * @param by 递增大小 > 0
  279 + * @return
  280 + */
  281 + public Double hincr(String key, String item, Double by) {
  282 + return redisTemplate.opsForHash().increment(key, item, by);
  283 + }
  284 +
  285 + /**
  286 + * Hash递减
  287 + * @param key 键
  288 + * @param item 项
  289 + * @param by 递减大小
  290 + * @return
  291 + */
  292 + public Double hdecr(String key, String item, Double by) {
  293 + return redisTemplate.opsForHash().increment(key, item, -by);
  294 + }
  295 +
  296 +// ============================== Set ==============================
  297 +
  298 + /**
  299 + * 根据 key 获取 set 中的所有值
  300 + * @param key 键
  301 + * @return 值
  302 + */
  303 + public Set<Object> sGet(String key) {
  304 + try {
  305 + return redisTemplate.opsForSet().members(key);
  306 + } catch (Exception e) {
  307 + e.printStackTrace();
  308 + return null;
  309 + }
  310 + }
  311 +
  312 + /**
  313 + * 从键为 key 的 set 中,根据 value 查询是否存在
  314 + * @param key 键
  315 + * @param value 值
  316 + * @return true / false
  317 + */
  318 + public boolean sHasKey(String key, Object value) {
  319 + try {
  320 + return redisTemplate.opsForSet().isMember(key, value);
  321 + } catch (Exception e) {
  322 + e.printStackTrace();
  323 + return false;
  324 + }
  325 + }
  326 +
  327 + /**
  328 + * 将数据放入 set缓存
  329 + * @param key 键值
  330 + * @param values 值(可以多个)
  331 + * @return 成功个数
  332 + */
  333 + public long sSet(String key, Object... values) {
  334 + try {
  335 + return redisTemplate.opsForSet().add(key, values);
  336 + } catch (Exception e) {
  337 + e.printStackTrace();
  338 + return 0;
  339 + }
  340 + }
  341 +
  342 + /**
  343 + * 将数据放入 set缓存,并设置时间
  344 + * @param key 键
  345 + * @param time 时间
  346 + * @param values 值(可以多个)
  347 + * @return 成功放入个数
  348 + */
  349 + public long sSet(String key, long time, Object... values) {
  350 + try {
  351 + long count = redisTemplate.opsForSet().add(key, values);
  352 + if (time > 0) {
  353 + expire(key, time);
  354 + }
  355 + return count;
  356 + } catch (Exception e) {
  357 + e.printStackTrace();
  358 + return 0;
  359 + }
  360 + }
  361 +
  362 + /**
  363 + * 获取 set缓存的长度
  364 + * @param key 键
  365 + * @return 长度
  366 + */
  367 + public long sGetSetSize(String key) {
  368 + try {
  369 + return redisTemplate.opsForSet().size(key);
  370 + } catch (Exception e) {
  371 + e.printStackTrace();
  372 + return 0;
  373 + }
  374 + }
  375 +
  376 + /**
  377 + * 移除 set缓存中,值为 value 的
  378 + * @param key 键
  379 + * @param values 值
  380 + * @return 成功移除个数
  381 + */
  382 + public long setRemove(String key, Object... values) {
  383 + try {
  384 + return redisTemplate.opsForSet().remove(key, values);
  385 + } catch (Exception e) {
  386 + e.printStackTrace();
  387 + return 0;
  388 + }
  389 + }
  390 +// ============================== ZSet ==============================
  391 +
  392 + /**
  393 + * 添加一个元素, zset与set最大的区别就是每个元素都有一个score,因此有个排序的辅助功能; zadd
  394 + *
  395 + * @param key
  396 + * @param value
  397 + * @param score
  398 + */
  399 + public void zAdd(Object key, Object value, double score) {
  400 + redisTemplate.opsForZSet().add(key, value, score);
  401 + }
  402 +
  403 + /**
  404 + * 删除元素 zrem
  405 + *
  406 + * @param key
  407 + * @param value
  408 + */
  409 + public void zRemove(Object key, Object value) {
  410 + redisTemplate.opsForZSet().remove(key, value);
  411 + }
  412 +
  413 + /**
  414 + * score的增加or减少 zincrby
  415 + *
  416 + * @param key
  417 + * @param value
  418 + * @param score
  419 + */
  420 + public Double zIncrScore(Object key, Object value, double score) {
  421 + return redisTemplate.opsForZSet().incrementScore(key, value, score);
  422 + }
  423 +
  424 + /**
  425 + * 查询value对应的score zscore
  426 + *
  427 + * @param key
  428 + * @param value
  429 + * @return
  430 + */
  431 + public Double zScore(Object key, Object value) {
  432 + return redisTemplate.opsForZSet().score(key, value);
  433 + }
  434 +
  435 + /**
  436 + * 判断value在zset中的排名 zrank
  437 + *
  438 + * @param key
  439 + * @param value
  440 + * @return
  441 + */
  442 + public Long zRank(Object key, Object value) {
  443 + return redisTemplate.opsForZSet().rank(key, value);
  444 + }
  445 +
  446 + /**
  447 + * 返回集合的长度
  448 + *
  449 + * @param key
  450 + * @return
  451 + */
  452 + public Long zSize(Object key) {
  453 + return redisTemplate.opsForZSet().zCard(key);
  454 + }
  455 +
  456 + /**
  457 + * 查询集合中指定顺序的值, 0 -1 表示获取全部的集合内容 zrange
  458 + *
  459 + * 返回有序的集合,score小的在前面
  460 + *
  461 + * @param key
  462 + * @param start
  463 + * @param end
  464 + * @return
  465 + */
  466 + public Set<Object> ZRange(Object key, int start, int end) {
  467 + return redisTemplate.opsForZSet().range(key, start, end);
  468 + }
  469 + /**
  470 + * 查询集合中指定顺序的值和score,0, -1 表示获取全部的集合内容
  471 + *
  472 + * @param key
  473 + * @param start
  474 + * @param end
  475 + * @return
  476 + */
  477 + public Set<ZSetOperations.TypedTuple<String>> zRangeWithScore(Object key, int start, int end) {
  478 + return redisTemplate.opsForZSet().rangeWithScores(key, start, end);
  479 + }
  480 + /**
  481 + * 查询集合中指定顺序的值 zrevrange
  482 + *
  483 + * 返回有序的集合中,score大的在前面
  484 + *
  485 + * @param key
  486 + * @param start
  487 + * @param end
  488 + * @return
  489 + */
  490 + public Set<String> zRevRange(Object key, int start, int end) {
  491 + return redisTemplate.opsForZSet().reverseRange(key, start, end);
  492 + }
  493 + /**
  494 + * 根据score的值,来获取满足条件的集合 zrangebyscore
  495 + *
  496 + * @param key
  497 + * @param min
  498 + * @param max
  499 + * @return
  500 + */
  501 + public Set<String> zSortRange(Object key, int min, int max) {
  502 + return redisTemplate.opsForZSet().rangeByScore(key, min, max);
  503 + }
  504 +
  505 +
  506 +// ============================== List ==============================
  507 +
  508 + /**
  509 + * 获取 list缓存的内容
  510 + * @param key 键
  511 + * @param start 开始
  512 + * @param end 结束(0 到 -1 代表所有值)
  513 + * @return
  514 + */
  515 + public List<Object> lGet(String key, long start, long end) {
  516 + try {
  517 + return redisTemplate.opsForList().range(key, start, end);
  518 + } catch (Exception e) {
  519 + e.printStackTrace();
  520 + return null;
  521 + }
  522 + }
  523 +
  524 + /**
  525 + * 获取 list缓存的长度
  526 + * @param key 键
  527 + * @return 长度
  528 + */
  529 + public long lGetListSize(String key) {
  530 + try {
  531 + return redisTemplate.opsForList().size(key);
  532 + } catch (Exception e) {
  533 + e.printStackTrace();
  534 + return 0;
  535 + }
  536 + }
  537 +
  538 + /**
  539 + * 根据索引 index 获取键为 key 的 list 中的元素
  540 + * @param key 键
  541 + * @param index 索引
  542 + * 当 index >= 0 时 {0:表头, 1:第二个元素}
  543 + * 当 index < 0 时 {-1:表尾, -2:倒数第二个元素}
  544 + * @return 值
  545 + */
  546 + public Object lGetIndex(String key, long index) {
  547 + try {
  548 + return redisTemplate.opsForList().index(key, index);
  549 + } catch (Exception e) {
  550 + e.printStackTrace();
  551 + return null;
  552 + }
  553 + }
  554 +
  555 + /**
  556 + * 将值 value 插入键为 key 的 list 中,如果 list 不存在则创建空 list
  557 + * @param key 键
  558 + * @param value 值
  559 + * @return true / false
  560 + */
  561 + public boolean lSet(String key, Object value) {
  562 + try {
  563 + redisTemplate.opsForList().rightPush(key, value);
  564 + return true;
  565 + } catch (Exception e) {
  566 + e.printStackTrace();
  567 + return false;
  568 + }
  569 + }
  570 +
  571 + /**
  572 + * 将值 value 插入键为 key 的 list 中,并设置时间
  573 + * @param key 键
  574 + * @param value 值
  575 + * @param time 时间
  576 + * @return true / false
  577 + */
  578 + public boolean lSet(String key, Object value, long time) {
  579 + try {
  580 + redisTemplate.opsForList().rightPush(key, value);
  581 + if (time > 0) {
  582 + expire(key, time);
  583 + }
  584 + return true;
  585 + } catch (Exception e) {
  586 + e.printStackTrace();
  587 + return false;
  588 + }
  589 + }
  590 +
  591 + /**
  592 + * 将 values 插入键为 key 的 list 中
  593 + * @param key 键
  594 + * @param values 值
  595 + * @return true / false
  596 + */
  597 + public boolean lSetList(String key, List<Object> values) {
  598 + try {
  599 + redisTemplate.opsForList().rightPushAll(key, values);
  600 + return true;
  601 + } catch (Exception e) {
  602 + e.printStackTrace();
  603 + return false;
  604 + }
  605 + }
  606 +
  607 + /**
  608 + * 将 values 插入键为 key 的 list 中,并设置时间
  609 + * @param key 键
  610 + * @param values 值
  611 + * @param time 时间
  612 + * @return true / false
  613 + */
  614 + public boolean lSetList(String key, List<Object> values, long time) {
  615 + try {
  616 + redisTemplate.opsForList().rightPushAll(key, values);
  617 + if (time > 0) {
  618 + expire(key, time);
  619 + }
  620 + return true;
  621 + } catch (Exception e) {
  622 + e.printStackTrace();
  623 + return false;
  624 + }
  625 + }
  626 +
  627 + /**
  628 + * 根据索引 index 修改键为 key 的值
  629 + * @param key 键
  630 + * @param index 索引
  631 + * @param value 值
  632 + * @return true / false
  633 + */
  634 + public boolean lUpdateIndex(String key, long index, Object value) {
  635 + try {
  636 + redisTemplate.opsForList().set(key, index, value);
  637 + return true;
  638 + } catch (Exception e) {
  639 + e.printStackTrace();
  640 + return false;
  641 + }
  642 + }
  643 +
  644 + /**
  645 + * 在键为 key 的 list 中删除值为 value 的元素
  646 + * @param key 键
  647 + * @param count 如果 count == 0 则删除 list 中所有值为 value 的元素
  648 + * 如果 count > 0 则删除 list 中最左边那个值为 value 的元素
  649 + * 如果 count < 0 则删除 list 中最右边那个值为 value 的元素
  650 + * @param value
  651 + * @return
  652 + */
  653 + public long lRemove(String key, long count, Object value) {
  654 + try {
  655 + return redisTemplate.opsForList().remove(key, count, value);
  656 + } catch (Exception e) {
  657 + e.printStackTrace();
  658 + return 0;
  659 + }
  660 + }
  661 +
  662 + /**
  663 + * 模糊查询
  664 + * @param key 键
  665 + * @return true / false
  666 + */
  667 + public List<Object> keys(String key) {
  668 + try {
  669 + Set<String> set = redisTemplate.keys(key);
  670 + return new ArrayList<>(set);
  671 + } catch (Exception e) {
  672 + e.printStackTrace();
  673 + return null;
  674 + }
  675 + }
  676 +
  677 +
  678 + /**
  679 + * 模糊查询
  680 + * @param query 查询参数
  681 + * @return
  682 + */
  683 +// public List<Object> scan(String query) {
  684 +// List<Object> result = new ArrayList<>();
  685 +// try {
  686 +// Cursor<Map.Entry<Object,Object>> cursor = redisTemplate.opsForHash().scan("field",
  687 +// ScanOptions.scanOptions().match(query).count(1000).build());
  688 +// while (cursor.hasNext()) {
  689 +// Map.Entry<Object,Object> entry = cursor.next();
  690 +// result.add(entry.getKey());
  691 +// Object key = entry.getKey();
  692 +// Object valueSet = entry.getValue();
  693 +// }
  694 +// //关闭cursor
  695 +// cursor.close();
  696 +// } catch (Exception e) {
  697 +// e.printStackTrace();
  698 +// }
  699 +// return result;
  700 +// }
  701 +
  702 + /**
  703 + * 模糊查询
  704 + * @param query 查询参数
  705 + * @return
  706 + */
  707 + public List<Object> scan(String query) {
  708 + Set<String> keys = (Set<String>) redisTemplate.execute((RedisCallback<Set<String>>) connection -> {
  709 + Set<String> keysTmp = new HashSet<>();
  710 + Cursor<byte[]> cursor = connection.scan(new ScanOptions.ScanOptionsBuilder().match(query).count(1000).build());
  711 + while (cursor.hasNext()) {
  712 + keysTmp.add(new String(cursor.next()));
  713 + }
  714 + return keysTmp;
  715 + });
  716 +// Set<String> keys = (Set<String>) redisTemplate.execute(new RedisCallback<Set<String>>(){
  717 +//
  718 +// @Override
  719 +// public Set<String> doInRedis(RedisConnection connection) throws DataAccessException {
  720 +// Set<String> keysTmp = new HashSet<>();
  721 +// Cursor<byte[]> cursor = connection.scan(new ScanOptions.ScanOptionsBuilder().match(query).count(1000).build());
  722 +// while (cursor.hasNext()) {
  723 +// keysTmp.add(new String(cursor.next()));
  724 +// }
  725 +// return keysTmp;
  726 +// }
  727 +// });
  728 +
  729 + return new ArrayList<>(keys);
  730 + }
  731 +
  732 +}
... ...
src/main/java/top/panll/assist/config/StartConfig.java
... ... @@ -31,6 +31,7 @@ public class StartConfig implements CommandLineRunner {
31 31 @Autowired
32 32 private VideoFileService videoFileService;
33 33  
  34 +
34 35 @Override
35 36 public void run(String... args) {
36 37 String record = userSettings.getRecord();
... ...
src/main/java/top/panll/assist/controller/RecordController.java 0 → 100644
  1 +package top.panll.assist.controller;
  2 +
  3 +import org.slf4j.Logger;
  4 +import org.slf4j.LoggerFactory;
  5 +import org.springframework.beans.factory.annotation.Autowired;
  6 +import org.springframework.web.bind.annotation.*;
  7 +import top.panll.assist.controller.bean.WVPResult;
  8 +import top.panll.assist.service.VideoFileService;
  9 +
  10 +import java.io.File;
  11 +import java.text.ParseException;
  12 +import java.text.SimpleDateFormat;
  13 +import java.util.ArrayList;
  14 +import java.util.Date;
  15 +import java.util.List;
  16 +
  17 +@CrossOrigin
  18 +@RestController
  19 +@RequestMapping("/api/record")
  20 +public class RecordController {
  21 +
  22 + private final static Logger logger = LoggerFactory.getLogger(RecordController.class);
  23 +
  24 + @Autowired
  25 + private VideoFileService videoFileService;
  26 +
  27 + private SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  28 +
  29 + /**
  30 + * 获取app文件夹列表
  31 + * @return
  32 + */
  33 + @GetMapping(value = "/app/list")
  34 + @ResponseBody
  35 + public WVPResult<List<String>> getAppList(){
  36 + WVPResult<List<String>> result = new WVPResult<>();
  37 + List<String> resultData = new ArrayList<>();
  38 + List<File> appList = videoFileService.getAppList();
  39 + for (File file : appList) {
  40 + resultData.add(file.getName());
  41 + }
  42 + result.setCode(0);
  43 + result.setMsg("success");
  44 + result.setData(resultData);
  45 + return result;
  46 + }
  47 +
  48 + /**
  49 + * 获取stream文件夹列表
  50 + * @return
  51 + */
  52 + @GetMapping(value = "/stream/list")
  53 + @ResponseBody
  54 + public WVPResult<List<String>> getStreamList(@RequestParam String app ){
  55 + WVPResult<List<String>> result = new WVPResult<>();
  56 + List<String> resultData = new ArrayList<>();
  57 + if (app == null) {
  58 + result.setCode(400);
  59 + result.setMsg("app不能为空");
  60 + return result;
  61 + }
  62 + List<File> streamList = videoFileService.getStreamList(app);
  63 + for (File file : streamList) {
  64 + resultData.add(file.getName());
  65 + }
  66 + result.setCode(0);
  67 + result.setMsg("success");
  68 + result.setData(resultData);
  69 + return result;
  70 + }
  71 +
  72 + /**
  73 + * 获取视频文件列表
  74 + * @return
  75 + */
  76 + @GetMapping(value = "/file/list")
  77 + @ResponseBody
  78 + public WVPResult<List<String>> getRecordList(@RequestParam String app,
  79 + @RequestParam String stream,
  80 + @RequestParam String startTime,
  81 + @RequestParam String endTime
  82 + ){
  83 +
  84 + WVPResult<List<String>> result = new WVPResult<>();
  85 + // TODO 暂时开始时间与结束时间为必传, 后续可不传或只传其一
  86 + List<String> recordList = new ArrayList<>();
  87 + try {
  88 + Date startTimeDate = null;
  89 + Date endTimeDate = null;
  90 + if (startTime != null ) {
  91 + startTimeDate = formatter.parse(startTime);
  92 + }
  93 + if (endTime != null ) {
  94 + endTimeDate = formatter.parse(endTime);
  95 + }
  96 +
  97 + List<File> filesInTime = videoFileService.getFilesInTime(app, stream, startTimeDate, endTimeDate);
  98 + if (filesInTime != null && filesInTime.size() > 0) {
  99 + for (File file : filesInTime) {
  100 + recordList.add(file.getName());
  101 + }
  102 + }
  103 + result.setCode(0);
  104 + result.setMsg("success");
  105 + result.setData(recordList);
  106 + } catch (ParseException e) {
  107 + logger.error("错误的开始时间[{}]或结束时间[{}]", startTime, endTime);
  108 + result.setCode(400);
  109 + result.setMsg("错误的开始时间或结束时间");
  110 + }
  111 + return result;
  112 + }
  113 +
  114 +
  115 + /**
  116 + * 添加视频裁剪合并任务
  117 + * @param app
  118 + * @param stream
  119 + * @param startTime
  120 + * @param endTime
  121 + * @return
  122 + */
  123 + @GetMapping(value = "/file/download/task")
  124 + @ResponseBody
  125 + public WVPResult<String> addTaskForDownload(@RequestParam String app,
  126 + @RequestParam String stream,
  127 + @RequestParam String startTime,
  128 + @RequestParam String endTime
  129 + ){
  130 + WVPResult<String> result = new WVPResult<>();
  131 + Date startTimeDate = null;
  132 + Date endTimeDate = null;
  133 + try {
  134 + if (startTime != null ) {
  135 + startTimeDate = formatter.parse(startTime);
  136 + }
  137 + if (endTime != null ) {
  138 + endTimeDate = formatter.parse(endTime);
  139 + }
  140 + } catch (ParseException e) {
  141 + e.printStackTrace();
  142 + }
  143 + String id = videoFileService.mergeOrCut(app, stream, startTimeDate, endTimeDate);
  144 + result.setCode(0);
  145 + result.setMsg(id!= null?"success":"error");
  146 + result.setData(id);
  147 + return result;
  148 + }
  149 +
  150 + /**
  151 + * 录制完成的通知
  152 + * @return
  153 + */
  154 + @GetMapping(value = "/end")
  155 + @ResponseBody
  156 + public WVPResult<String> recordEnd(@RequestParam String path
  157 + ){
  158 + File file = new File(path);
  159 + WVPResult<String> result = new WVPResult<>();
  160 + if (file.exists()) {
  161 + try {
  162 + videoFileService.handFile(file);
  163 + result.setCode(0);
  164 + result.setMsg("success");
  165 + } catch (ParseException e) {
  166 + e.printStackTrace();
  167 + result.setCode(500);
  168 + result.setMsg("error");
  169 + }
  170 + }else {
  171 + result.setCode(400);
  172 + result.setMsg("路径不存在");
  173 + }
  174 + return result;
  175 + }
  176 +}
... ...
src/main/java/top/panll/assist/controller/bean/WVPResult.java 0 → 100644
  1 +package top.panll.assist.controller.bean;
  2 +
  3 +public class WVPResult<T> {
  4 +
  5 + private int code;
  6 + private String msg;
  7 + private T data;
  8 +
  9 + public int getCode() {
  10 + return code;
  11 + }
  12 +
  13 + public void setCode(int code) {
  14 + this.code = code;
  15 + }
  16 +
  17 + public String getMsg() {
  18 + return msg;
  19 + }
  20 +
  21 + public void setMsg(String msg) {
  22 + this.msg = msg;
  23 + }
  24 +
  25 + public T getData() {
  26 + return data;
  27 + }
  28 +
  29 + public void setData(T data) {
  30 + this.data = data;
  31 + }
  32 +}
... ...
src/main/java/top/panll/assist/service/FFmpegExecUtils.java
... ... @@ -43,27 +43,29 @@ public class FFmpegExecUtils {
43 43 void run(String status, double percentage, String result);
44 44 }
45 45  
46   - public void mergeOrCutFile(List<File> fils, File dest, VideoHandEndCallBack callBack){
  46 + public String mergeOrCutFile(List<File> fils, File dest, String temp, VideoHandEndCallBack callBack){
47 47 FFmpeg ffmpeg = FFmpegExecUtils.getInstance().ffmpeg;
48 48 FFprobe ffprobe = FFmpegExecUtils.getInstance().ffprobe;
49 49 if (fils == null || fils.size() == 0 || ffmpeg == null || ffprobe == null || dest== null || !dest.exists()){
50 50 callBack.run("error", 0.0, null);
51   - return;
  51 + return null;
52 52 }
53 53  
54   - String temp = DigestUtils.md5DigestAsHex(String.valueOf(System.currentTimeMillis()).getBytes());
  54 +
55 55 File tempFile = new File(dest.getAbsolutePath() + File.separator + temp);
56 56 if (!tempFile.exists()) {
57 57 tempFile.mkdirs();
58 58 }
59 59 FFmpegExecutor executor = new FFmpegExecutor(ffmpeg, ffprobe);
60   - String fileListName = tempFile.getName() + File.separator + "fileList";
  60 + String fileListName = tempFile.getAbsolutePath() + File.separator + "fileList";
61 61 double durationAll = 0.0;
62 62 try {
63 63 BufferedWriter bw =new BufferedWriter(new FileWriter(fileListName));
64 64 for (File file : fils) {
65   - FFmpegProbeResult in = ffprobe.probe(file.getAbsolutePath());
66   - double duration = in.getFormat().duration;
  65 + String[] split = file.getName().split("-");
  66 + if (split.length != 3) continue;
  67 + String durationStr = split[2].replace(".mp4", "");
  68 + Double duration = Double.parseDouble(durationStr)/1000;
67 69 System.out.println(duration);
68 70 bw.write("file " + file.getAbsolutePath());
69 71 bw.newLine();
... ... @@ -76,6 +78,7 @@ public class FFmpegExecUtils {
76 78 e.printStackTrace();
77 79 callBack.run("error", 0.0, null);
78 80 }
  81 + String recordFileResultPath = dest.getAbsolutePath() + File.separator + temp + File.separator + "record.mp4";
79 82 long startTime = System.currentTimeMillis();
80 83 FFmpegBuilder builder = new FFmpegBuilder()
81 84  
... ... @@ -83,10 +86,9 @@ public class FFmpegExecUtils {
83 86 .overrideOutputFiles(true)
84 87 .setInput(fileListName) // Or filename
85 88 .addExtraArgs("-safe", "0")
86   - .addOutput(dest.getAbsolutePath() + File.separator + temp + ".mp4")
  89 + .addOutput(recordFileResultPath)
87 90 .setVideoCodec("copy")
88 91 .setFormat("mp4")
89   -
90 92 .done();
91 93  
92 94  
... ... @@ -96,18 +98,17 @@ public class FFmpegExecUtils {
96 98 double percentage = progress.out_time_ns / duration_ns;
97 99  
98 100 // Print out interesting information about the progress
99   - System.out.println(String.format(
100   - "[%.0f%%] status:%s frame:%d time:%s ms fps:%.0f speed:%.2fx",
101   - percentage * 100,
102   - progress.status,
103   - progress.frame,
104   - FFmpegUtils.toTimecode(progress.out_time_ns, TimeUnit.NANOSECONDS),
105   - progress.fps.doubleValue(),
106   - progress.speed
107   - ));
  101 +// System.out.println(String.format(
  102 +// "[%.0f%%] status:%s frame:%d time:%s ms fps:%.0f speed:%.2fx",
  103 +// percentage * 100,
  104 +// progress.status,
  105 +// progress.frame,
  106 +// FFmpegUtils.toTimecode(progress.out_time_ns, TimeUnit.NANOSECONDS),
  107 +// progress.fps.doubleValue(),
  108 +// progress.speed
  109 +// ));
108 110 if (progress.status.equals(Progress.Status.END)){
109   - callBack.run(progress.status.name(), percentage,
110   - dest.getAbsolutePath() + File.separator + temp + ".mp4");
  111 + callBack.run(progress.status.name(), percentage,dest.getName() + File.separator + temp + File.separator + "record.mp4");
111 112 System.out.println(System.currentTimeMillis() - startTime);
112 113 }else {
113 114 callBack.run(progress.status.name(), percentage, null);
... ... @@ -115,6 +116,7 @@ public class FFmpegExecUtils {
115 116 });
116 117  
117 118 job.run();
  119 + return temp;
118 120 }
119 121  
120 122 }
... ...
src/main/java/top/panll/assist/service/VideoFileService.java
... ... @@ -6,18 +6,23 @@ import net.bramp.ffmpeg.progress.Progress;
6 6 import org.slf4j.Logger;
7 7 import org.slf4j.LoggerFactory;
8 8 import org.springframework.beans.factory.annotation.Autowired;
  9 +import org.springframework.context.annotation.Bean;
  10 +import org.springframework.data.redis.core.StringRedisTemplate;
  11 +import org.springframework.data.redis.listener.RedisMessageListenerContainer;
9 12 import org.springframework.stereotype.Service;
10   -import top.panll.assist.config.StartConfig;
  13 +import org.springframework.util.DigestUtils;
  14 +import top.panll.assist.config.RedisUtil;
11 15 import top.panll.assist.dto.UserSettings;
12 16 import top.panll.assist.utils.DateUtils;
13 17  
14 18 import java.io.File;
15   -import java.io.FilenameFilter;
16 19 import java.io.IOException;
17   -import java.lang.reflect.Array;
18 20 import java.text.ParseException;
19 21 import java.text.SimpleDateFormat;
20 22 import java.util.*;
  23 +import java.util.concurrent.LinkedBlockingQueue;
  24 +import java.util.concurrent.ThreadPoolExecutor;
  25 +import java.util.concurrent.TimeUnit;
21 26  
22 27 @Service
23 28 public class VideoFileService {
... ... @@ -27,6 +32,46 @@ public class VideoFileService {
27 32 @Autowired
28 33 private UserSettings userSettings;
29 34  
  35 + @Autowired
  36 + private RedisUtil redisUtil;
  37 +
  38 + @Autowired
  39 + private StringRedisTemplate stringRedisTemplate;
  40 +
  41 + private ThreadPoolExecutor processThreadPool;
  42 +
  43 +
  44 + @Bean("iniThreadPool")
  45 + private ThreadPoolExecutor iniThreadPool() {
  46 +
  47 + int processThreadNum = Runtime.getRuntime().availableProcessors() * 10;
  48 + LinkedBlockingQueue<Runnable> processQueue = new LinkedBlockingQueue<Runnable>(10000);
  49 + processThreadPool = new ThreadPoolExecutor(processThreadNum,processThreadNum,
  50 + 0L, TimeUnit.MILLISECONDS,processQueue,
  51 + new ThreadPoolExecutor.CallerRunsPolicy());
  52 + return processThreadPool;
  53 + }
  54 +
  55 +
  56 + public List<File> getAppList() {
  57 + File recordFile = new File(userSettings.getRecord());
  58 + if (recordFile != null) {
  59 + File[] files = recordFile.listFiles();
  60 + return Arrays.asList(files);
  61 + }else {
  62 + return null;
  63 + }
  64 + }
  65 +
  66 + public List<File> getStreamList(String app) {
  67 + File appFile = new File(userSettings.getRecord() + File.separator + app);
  68 + if (appFile != null) {
  69 + File[] files = appFile.listFiles();
  70 + return Arrays.asList(files);
  71 + }else {
  72 + return null;
  73 + }
  74 + }
30 75  
31 76 /**
32 77 * 对视频文件重命名, 00:00:00-00:00:00
... ... @@ -35,10 +80,10 @@ public class VideoFileService {
35 80 */
36 81 public void handFile(File file) throws ParseException {
37 82 FFprobe ffprobe = FFmpegExecUtils.getInstance().ffprobe;
38   - if(file.isFile() && !file.getName().startsWith(".") && file.getName().indexOf(":") < 0) {
  83 + if(file.isFile() && !file.getName().startsWith(".")&& file.getName().endsWith(".mp4") && file.getName().indexOf(":") < 0) {
39 84 try {
40 85 FFmpegProbeResult in = null;
41   - in = ffprobe.probe(file.getAbsolutePath());
  86 + in = ffprobe.probe(file.getAbsolutePath());
42 87 double duration = in.getFormat().duration * 1000;
43 88 String endTimeStr = file.getName().replace(".mp4", "");
44 89  
... ... @@ -47,11 +92,13 @@ public class VideoFileService {
47 92  
48 93 File dateFile = new File(file.getParent());
49 94  
50   - long endTime = formatter.parse(dateFile.getName() + " " + endTimeStr).getTime();
51   - long startTime = endTime - new Double(duration).longValue();
  95 + long startTime = formatter.parse(dateFile.getName() + " " + endTimeStr).getTime();
  96 + long durationLong = new Double(duration).longValue();
  97 + long endTime = startTime + durationLong;
  98 + endTime = endTime - endTime%1000;
52 99  
53 100 String newName = file.getAbsolutePath().replace(file.getName(),
54   - simpleDateFormat.format(startTime) + "-" + simpleDateFormat.format(endTime) + ".mp4");
  101 + simpleDateFormat.format(startTime) + "-" + simpleDateFormat.format(endTime) + "-" + durationLong + ".mp4");
55 102 file.renameTo(new File(newName));
56 103 System.out.println(newName);
57 104 } catch (IOException exception) {
... ... @@ -98,7 +145,8 @@ public class VideoFileService {
98 145 logger.error("过滤日期文件时异常: {}-{}", name, e.getMessage());
99 146 return false;
100 147 }
101   - return DateUtils.getStartOfDay(fileDate).after(startTime) && DateUtils.getEndOfDay(fileDate).before(endTime);
  148 + return (DateUtils.getStartOfDay(fileDate).compareTo(startTime) >= 0
  149 + && DateUtils.getStartOfDay(fileDate).compareTo(endTime) <= 0) ;
102 150 });
103 151  
104 152 if (dateFiles != null && dateFiles.length > 0) {
... ... @@ -108,7 +156,7 @@ public class VideoFileService {
108 156 boolean filterResult = false;
109 157 if (name.contains(":") && name.endsWith(".mp4") && !name.startsWith(".")){
110 158 String[] timeArray = name.split("-");
111   - if (timeArray.length == 2){
  159 + if (timeArray.length == 3){
112 160 String fileStartTimeStr = dateFile.getName() + " " + timeArray[0];
113 161 String fileEndTimeStr = dateFile.getName() + " " + timeArray[1];
114 162 try {
... ... @@ -128,36 +176,50 @@ public class VideoFileService {
128 176 }
129 177 if (result.size() > 0) {
130 178 result.sort((File f1, File f2) -> {
131   - boolean sortResult = false;
  179 + int sortResult = 0;
132 180 String[] timeArray1 = f1.getName().split("-");
133 181 String[] timeArray2 = f2.getName().split("-");
134   - if (timeArray1.length == 2 && timeArray2.length == 2){
  182 + if (timeArray1.length == 3 && timeArray2.length == 3){
135 183 File dateFile1 = f1.getParentFile();
136 184 File dateFile2 = f2.getParentFile();
137 185 String fileStartTimeStr1 = dateFile1.getName() + " " + timeArray1[0];
138   - String fileStartTimeStr2 = dateFile2.getName() + " " + timeArray1[0];
  186 + String fileStartTimeStr2 = dateFile2.getName() + " " + timeArray2[0];
139 187 try {
140   - sortResult = formatter.parse(fileStartTimeStr1).before(formatter.parse(fileStartTimeStr2)) ;
  188 + sortResult = formatter.parse(fileStartTimeStr1).compareTo(formatter.parse(fileStartTimeStr2));
141 189 } catch (ParseException e) {
142   - logger.error("排序视频文件时异常: {}-{}", fileStartTimeStr1, fileStartTimeStr2);
  190 + e.printStackTrace();
143 191 }
144 192 }
145   - return sortResult?1 : 0;
  193 + return sortResult;
146 194 });
147 195 }
148 196 return result;
149 197 }
150 198  
151 199  
152   - public void mergeOrCut(String app, String stream, Date startTime, Date endTime) {
  200 + public String mergeOrCut(String app, String stream, Date startTime, Date endTime) {
153 201 List<File> filesInTime = this.getFilesInTime(app, stream, startTime, endTime);
154   - File recordFile = new File(userSettings.getRecord());
155   - File streamFile = new File(recordFile.getAbsolutePath() + File.separator + app + File.separator + stream);
156   - FFmpegExecUtils.getInstance().mergeOrCutFile(filesInTime, streamFile, (String status, double percentage, String result)->{
157   - if (status.equals(Progress.Status.END.name())) {
158   -
159   - }
  202 + File recordFile = new File(new File(userSettings.getRecord()).getParentFile().getAbsolutePath() + File.separator + "recordTemp");
  203 + if (!recordFile.exists()) recordFile.mkdirs();
  204 +
  205 + String temp = DigestUtils.md5DigestAsHex(String.valueOf(System.currentTimeMillis()).getBytes());
  206 + processThreadPool.execute(() -> {
  207 + FFmpegExecUtils.getInstance().mergeOrCutFile(filesInTime, recordFile, temp, (String status, double percentage, String result)->{
  208 + Map<String, String> data = new HashMap<>();
  209 + data.put("id", temp);
  210 + // 发出redis通知
  211 + if (status.equals(Progress.Status.END.name())) {
  212 + data.put("percentage", "1");
  213 + data.put("recordFile", result);
  214 + redisUtil.set(app + "_" + stream + "_" + temp, data, 3*60*60);
  215 + stringRedisTemplate.convertAndSend("topic_mergeorcut_end", data);
  216 + }else {
  217 + data.put("percentage", percentage + "");
  218 + redisUtil.set(app + "_" + stream + "_" + temp, data, 3*60*60);
  219 + stringRedisTemplate.convertAndSend("topic_mergeorcut_continue", data);
  220 + }
  221 + });
160 222 });
  223 + return temp;
161 224 }
162   -
163 225 }
... ...
src/main/resources/application-local.yml
  1 +spring:
  2 + # REDIS数据库配置
  3 + redis:
  4 + # [必须修改] Redis服务器IP, REDIS安装在本机的,使用127.0.0.1
  5 + host: 127.0.0.1
  6 + # [必须修改] 端口号
  7 + port: 6379
  8 + # [可选] 数据库 DB
  9 + database: 8
  10 + # [可选] 访问密码,若你的redis服务器没有设置密码,就不需要用密码去连接
  11 + password:
  12 + # [可选] 超时时间
  13 + timeout: 10000
  14 +
1 15 # [可选] WVP监听的HTTP端口, 网页和接口调用都是这个端口
2 16 server:
3 17 port: 18081
... ... @@ -31,7 +45,6 @@ logging:
31 45 level:
32 46 com:
33 47 genersoft:
34   - iot: INFO
35   - net:
36   - bramp:
37   - ffmpeg: WARN
38 48 \ No newline at end of file
  49 + iot: info
  50 + net:
  51 + bramp: warning
39 52 \ No newline at end of file
... ...