修改并完善淡白影视代码
- 作者:
- 淡白
- 创建时间:
- 2019-10-13 16:39:44
- 影视网站
摘要:该项目是为了巩固学习Spring框架而创建的一个影视项目。在项目中使用了阿里代码检测插件检查代码规范并进行修改,实现了访问统计的功能,并使用Redis存储数据。同时也实现了从腾讯视频获取弹幕的功能,并通过视频播放器Dplayer展示弹幕。最后还实现了将弹幕保存到数据库的功能,并通过异步任务线程池和任务调度实现了定时任务。在项目中还遇到了时区问题,通过修改配置文件解决了该问题。
学习了spring框架之后为了巩固下基础就写了淡白影视这样一个项目
用阿里代码检测插件检测代码规范并修改
使用未定义常量”user” 给接口、方法、类添加注释
实现访问统计
使用redis存储数据所以先配置所需依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
Application
redis:
database: 0
host: 39.108.110.44
password: ****
port: 6379
timeout: 0
配置redisConfig
package com.danbai.ys.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
/**
* @author danbai
* @date 2019-10-14 14:36
*/
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
@Bean
//如使用注解的话需要配置cacheManager
CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
//初始化一个RedisCacheWriter
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory);
RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig();
//设置默认超过期时间是1天
defaultCacheConfig.entryTtl(Duration.ofDays(1));
//初始化RedisCacheManager
RedisCacheManager cacheManager = new RedisCacheManager(redisCacheWriter, defaultCacheConfig);
return cacheManager;
}
// 以下两种redisTemplate自由根据场景选择
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
//使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
serializer.setObjectMapper(mapper);
template.setValueSerializer(serializer);
//使用StringRedisSerializer来序列化和反序列化redis的key值
template.setKeySerializer(new StringRedisSerializer());
template.afterPropertiesSet();
return template;
}
@Bean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory) {
StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();
stringRedisTemplate.setConnectionFactory(factory);
return stringRedisTemplate;
}
}
测试类
@RunWith(SpringRunner.class)
@SpringBootTest(classes = YsApplication.class)
public class YsApplicationTests {
@Autowired
RedisTemplate redisTemplate;
@Test
public void redis() {
//这里相当于redis对String类型的set操作
redisTemplate.opsForValue().set("test","哈喽!");
//这里相当于redis对String类型的get操作
String test = (String)redisTemplate.opsForValue().get("test");
System.out.println(test);
}
}
然后在redis新建一个key为TotalAccess值为1的数据
redisTemplate.opsForValue().increment("TotalAccess");
就能增加访问统计了 reids相关使用看了下这篇文章 接下来在Controller添加
@ModelAttribute
void count(HttpServletRequest request) {
String ip = IpUtils.getIpAddr(request);
if (!statistical.isIpInTheDatabase(ip)) {
statistical.addIp(ip);
statistical.addAccess();
}
}
统计服务实现
@Service
public class StatisticalImpl implements Statistical {
@Autowired
RedisTemplate redisTemplate;
@Override
public void addAccess() {
redisTemplate.opsForValue().increment("TotalAccess");
}
@Override
public boolean isIpInTheDatabase(String ip) {
return redisTemplate.opsForSet().isMember(IpUtils.getDay(), ip);
}
@Override
public void addIp(String ip) {
redisTemplate.opsForSet().add(IpUtils.getDay(), ip);
redisTemplate.opsForValue().increment(IpUtils.getDay() + "-Access");
redisTemplate.expire(IpUtils.getDay(),1,TimeUnit.DAYS);
redisTemplate.expire(IpUtils.getDay() + "-Access",1,TimeUnit.DAYS);
}
@Override
public int getAccess() {
return (int) redisTemplate.opsForValue().get("TotalAccess");
}
@Override
public int getDayAccess() {
return (int) redisTemplate.opsForValue().get(IpUtils.getDay() + "-Access");
}
}
增加弹幕获取功能
从腾讯视频获取弹幕 实现方式 首先当用户请求影视页面时候通过请求id获取影视信息,然后再在腾讯视频搜索视频.获取腾讯视频的影视id再通过
http://s.video.qq.com/get_playsource?plat=2&type=4&range=1&otype=json&id=" + id
获取影视的视频列表 解析json获取其中视频id再通过接口 http://bullet.video.qq.com/fcgi-bin/target/regist?otype=json&vid=影视id 获取弹幕id (targetid)
QZOutputJson={"danmukey":"bubble_flag=1&targetid=4193003793&vid=c0032vrylb9&type=2","display":1,"is_has_adv":0,"is_has_bubble":1,"open":1,"returncode":0,"returnmsg":"OK","targetid":"4193003793"};
最后通过接口 targetid+时间获取弹幕列表添加到视频播放器 https://mfm.video.qq.com/danmu?otype=json&target_id=4193003793×tamp=360
最后实现结果 这次功能中遇到的困难不少,如跨域问题ip代理问题还有播放器如何添加弹幕等等. 详情阅读源码
实现弹幕保存到数据库
在向服务器请求弹幕id的时候判断服务器是否保存过该影视的弹幕. 存在: 返回null 不存在: 将请求信息存入redis
实现异步任务线程池
创建异步线程池配置类
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(20);
executor.setMaxPoolSize(100);
executor.setQueueCapacity(100);
executor.setKeepAliveSeconds(60);
executor.setThreadNamePrefix("taskExecutor-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
给Application添加异步和任务调度开启注解
@EnableScheduling
@EnableAsync
实现调度任务
创建Scheduler 任务调度类
@Component
@Configurable
public class Scheduler {
@Autowired
RedisTemplate redisTemplate;
@Autowired
private Dmas testas;
@Scheduled(fixedDelay = 10000)
@Async
public void cronJobSchedule() {
Set tagids = redisTemplate.opsForSet().members("tagids");
redisTemplate.delete("tagids");
Object[] das = tagids.toArray();
System.out.println(das.length);
for (Object s : das) {
JSONObject jsonObject = JSON.parseObject(String.valueOf(s));
String tagid = jsonObject.getString("tagid");
redisTemplate.opsForSet().add("oktagids", tagid);
String player = (jsonObject.getString("player"));
redisTemplate.delete("danmaku"+player);
int timestamp = 0;
boolean flg=true;
while (flg) {
String url = "http://mfm.video.qq.com/danmu?otype=json&target_id=" + tagid + "×tamp=" + timestamp;
timestamp += 30;
testas.xzbcdm(url,player);
if(timestamp>60*120){
flg=false;
}
}
}
}
}
异步任务类
package com.danbai.ys.async;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.danbai.ys.entity.Dan;
import com.danbai.ys.utils.HtmlUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import static java.lang.System.currentTimeMillis;
/**
* @author danbai
* @date 2019-10-17 09:12
*/
@Component
public class Dmas {
@Autowired
MongoTemplate mongoTemplate;
@Async
public void xzbcdm(String url, String player){
String json = HtmlUtils.getHtmlContentNp(url);
JSONObject jsonObject = JSON.parseObject(json);
JSONArray comments = jsonObject.getJSONArray("comments");
int maxc = comments.size();
for (int j = 0; j < maxc; j++) {
JSONObject comment = comments.getJSONObject(j);
Dan d = new Dan();
d.setReferer("https://v.qq.com");
d.setIp("::ffff:111.111.111.111");
d.setType(0);
d.setTime(comment.getDouble("timepoint"));
d.setAuthor(comment.getString("opername"));
d.setPlayer(player);
d.setText(comment.getString("content"));
d.setColor(14277107);
d.setDate(currentTimeMillis());
mongoTemplate.insert(d);
}
}
}
大概意思就是上次任务结束10秒钟后(启动应用执行一次,每次执行在上一次执行10后)执行一次.
检查redis任务队列是否有任务
有就执行任务并将弹幕数据存入mongodb
执行的任务开启@Async
表示异步执行
值得注意的是异步任务不能调用当前类中方法异步执行.
获取到的弹幕数据
时区问题
在前端请求后台观看历史记录时发现时间数据相差8小时 数据库
原因
spring中对于@RestController或者@Controller+@ResponseBody 注解的接口方法的返回值默认是Json格式,
所以当对于date类型的数据,在返回浏览器端是会被spring-boot 默认的Jackson框架转换,而Jackson框架默认的时区GMT(相对于中国是少了8小时)。
解决方法
在配置下多增加一个配置 第二个用于转换格式(可不用)
jackson:
time: GMT+8
date-format: yyyy-MM-dd HH:mm:ss