SpringBoot 仿抖音短视频小程序开发(一):项目的简介 (https://segmentfault.com/a/11…SpringBoot 仿抖音短视频小程序开发(二):项目功能分析与具体实现 (https://segmentfault.com/a/11… 源代码:SpringBoot 仿抖音短视频小程序开发 全栈式实战项目(https://gitee.com/scau_zns/sh…)短视频后台管理系统:(https://gitee.com/scau_zns/sh…
小程序的后台管理系统
涉及的技术栈:Bootstrap + jQuery + jGrid + SSM 框架 + zookeeper
一、用户列表的获取与分页
前端代码:
<div class=”usersList_wrapper”>
<!– 用户列表展示的表格 –>
<table id=”usersList”></table>
<!– 底部的分页条 –>
<div id=”usersListPager”></div>
</div>
jGrid 发送请求获取数据封装好展示到页面
// 用户列表
var handleList = function() {
// 上下文对象路径
var hdnContextPath = $(“#hdnContextPath”).val();
var apiServer = $(“#apiServer”).val();
var jqGrid = $(“#usersList”);
jqGrid.jqGrid({
caption: “ 短视频用户列表 ”,
url: hdnContextPath + “/users/list.action”,
mtype: “post”,
styleUI: ‘Bootstrap’,// 设置 jqgrid 的全局样式为 bootstrap 样式
datatype: “json”,
colNames: [‘ID’, ‘ 头像 ’, ‘ 用户名 ’, ‘ 昵称 ’, ‘ 粉丝数 ’, ‘ 关注数 ’, ‘ 获赞数 ’],
colModel: [
{name: ‘id’, index: ‘id’, width: 30, sortable: false, hidden: false},
{name: ‘faceImage’, index: ‘username’, width: 50, sortable: false,
formatter:function(cellvalue, options, rowObject) {
<!– 配置的虚拟目录 apiServer = http://192.168.199.150:8080 –>
var src = apiServer + cellvalue;
var img = “<img src='” + src + “‘ width=’120’></img>”
return img;
}
},
{name: ‘username’, index: ‘password’, width: 30, sortable: false},
{name: ‘nickname’, index: ‘nickname’, width: 30, sortable: false},
{name: ‘fansCounts’, index: ‘age’, width: 20, sortable: false},
{name: ‘followCounts’, index: ‘sexValue’, width: 20, sortable: false},
{name: ‘receiveLikeCounts’, index: ‘province’, width: 20, sortable: false, hidden: false}
],
viewrecords: true, // 定义是否要显示总记录数
rowNum: 10, // 在 grid 上显示记录条数,这个参数是要被传递到后台
rownumbers: true, // 如果为 ture 则会在表格左边新增一列,显示行顺序号,从 1 开始递增。此列名为 ’rn’
autowidth: true, // 如果为 ture 时,则当表格在首次被创建时会根据父元素比例重新调整表格宽度。如果父元素宽度改变,为了使表格宽度能够自动调整则需要实现函数:setGridWidth
height: 500, // 表格高度,可以是数字,像素值或者百分比
rownumWidth: 36, // 如果 rownumbers 为 true,则可以设置行号 的宽度
pager: “#usersListPager”, // 分页控件的 id
subGrid: false // 是否启用子表格
}).navGrid(‘#usersListPager’, {
edit: false,
add: false,
del: false,
search: false
});
// 随着窗口的变化,设置 jqgrid 的宽度
$(window).bind(‘resize’, function () {
var width = $(‘.usersList_wrapper’).width()*0.99;
jqGrid.setGridWidth(width);
});
// 不显示水平滚动条
jqGrid.closest(“.ui-jqgrid-bdiv”).css({“overflow-x” : “hidden”});
// 条件查询所有用户列表
$(“#searchUserListButton”).click(function(){
var searchUsersListForm = $(“#searchUserListForm”);
jqGrid.jqGrid().setGridParam({
page: 1,
url: hdnContextPath + “/users/list.action?” + searchUsersListForm.serialize(),
}).trigger(“reloadGrid”);
});
}
后端获取用户列表分页数据的接口:
@PostMapping(“/list”)
@ResponseBody
public PagedResult list(Users user , Integer page) {
PagedResult result = usersService.queryUsers(user, page == null ? 1 : page, 10);
return result;
}
搜索功能的实现:
<!– 搜索内容 –>
<div class=”col-md-12″>
<br/>
<form id=”searchUserListForm” class=”form-inline” method=”post” role=”form”>
<div class=”form-group”>
<label class=”sr-only” for=”username”> 用户名:</label>
<input id=”username” name=”username” type=”text” class=”form-control” placeholder=” 用户名 ” />
</div>
<div class=”form-group”>
<label class=”sr-only” for=”nickname”> 昵称:</label>
<input id=”nickname” name=”nickname” type=”text” class=”form-control” placeholder=” 昵称 ” />
</div>
<button id=”searchUserListButton” class=”btn yellow-casablanca” type=”button”> 搜 索 </button>
</form>
</div>
使用 jGrid 发送请求给后台
// 条件查询所有用户列表
$(“#searchUserListButton”).click(function(){
var searchUsersListForm = $(“#searchUserListForm”);
jqGrid.jqGrid().setGridParam({
page: 1,
url: hdnContextPath + “/users/list.action?” + searchUsersListForm.serialize(),
}).trigger(“reloadGrid”);
});
二、背景音乐 BGM 的上传、查询和删除
上传
$(“#file”).fileupload({
pasteZone: “#bgmContent”,
dataType: “json”,
done: function(e, data) {
console.log(data);
if (data.result.status != ‘200’) {
alert(“ 长传失败 …”);
} else {
var bgmServer = $(“#bgmServer”).val();
var url = bgmServer + data.result.data;
$(“#bgmContent”).html(“<a href='” + url + “‘ target=’_blank’> 点我播放 </a>”);
$(“#path”).attr(“value”, data.result.data);
}
}
});
后台接口保存 BGM 的方法参考上传头像的方法
分页查询
参考用户列表信息的分页查询多少
删除 BGM
var deleteBgm = function(bgmId) {
var flag = window.confirm(“ 是否确认删除???”);
if (!flag) {
return;
}
$.ajax({
url: $(“#hdnContextPath”).val() + ‘/video/delBgm.action?bgmId=’ + bgmId,
type: “POST”,
success: function(data) {
if (data.status == 200 && data.msg == ‘OK’) {
alert(‘ 删除成功~~’);
var jqGrid = $(“#bgmList”);
jqGrid.jqGrid().trigger(“reloadGrid”);
}
}
})
}
三、举报管理
禁止播放
var forbidVideo = function(videoId) {
var flag = window.confirm(“ 是否禁播 ”);
if (!flag) {
return;
}
$.ajax({
url: $(“#hdnContextPath”).val() + “/video/forbidVideo.action?videoId=” + videoId,
type: “POST”,
async: false,
success: function(data) {
if(data.status == 200 && data.msg == “OK”) {
alert(“ 操作成功 ”);
var jqGrid = $(“#usersReportsList”);
//reloadGrid 是重新加载表格
jqGrid.jqGrid().trigger(“reloadGrid”);
} else {
console.log(JSON.stringify(data));
}
}
})
}
四、后台管理系统增加或删除 BGM,向 zookeeper-server 创建子节点,让小程序后端监听【重点】
1、首先安装 Zookeeper 到 Linux 上,启动服务器
2、编写 zk 客户端代码:
import org.apache.curator.framework.CuratorFramework;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.ZooDefs.Ids;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ZKCurator {
//zk 客户端
private CuratorFramework client = null;
final static Logger log = LoggerFactory.getLogger(ZKCurator.class);
public ZKCurator(CuratorFramework client) {
this.client = client;
}
public void init() {
client = client.usingNamespace(“admin”);
try {
// 判断在 admin 命名空间下是否有 bgm 节点 /admin/bgm
if(client.checkExists().forPath(“/bgm”) == null ) {
// 对于 zk 来讲,有两种类型的节点,一种是持久节点(永久存在,除非手动删除),另一种是临时节点(会话断开,自动删除)
client.create().creatingParentContainersIfNeeded()
.withMode(CreateMode.PERSISTENT) // 持久节点
.withACL(Ids.OPEN_ACL_UNSAFE) // 匿名权限
.forPath(“/bgm”);
log.info(“zookeeper 客户端连接初始化成功 ”);
log.info(“zookeeper 服务端状态:{}”,client.isStarted());
}
} catch (Exception e) {
log.error(“zookeeper 客户端连接初始化失败 ”);
e.printStackTrace();
}
}
/**
* 增加或者删除 Bgm,向 zk-server 创建子节点,供小程序后端监听
* @param bgmId
* @param operType
*/
public void sendBgmOperator(String bgmId, String operObject) {
try {
client.create().creatingParentContainersIfNeeded()
.withMode(CreateMode.PERSISTENT) // 持久节点
.withACL(Ids.OPEN_ACL_UNSAFE) // 匿名权限
.forPath(“/bgm/” + bgmId, operObject.getBytes());
} catch (Exception e) {
e.printStackTrace();
}
}
}
3、在 applicationContext-zookeeper.xml 配置 zookeeper:
<!– 创建重连策列 –>
<bean id=”retryPolicy” class=”org.apache.curator.retry.ExponentialBackoffRetry”>
<!– 每次重试连接的等待时间 –>
<constructor-arg index=”0″ value=”1000″></constructor-arg>
<!– 设置最大的重连次数 –>
<constructor-arg index=”1″ value=”5″></constructor-arg>
</bean>
<!– 创建 zookeeper 客户端 –>
<bean id=”client” class=”org.apache.curator.framework.CuratorFrameworkFactory”
factory-method=”newClient” init-method=”start”>
<constructor-arg index=”0″ value=”120.79.18.35:2181″></constructor-arg>
<constructor-arg index=”1″ value=”10000″></constructor-arg>
<constructor-arg index=”2″ value=”10000″></constructor-arg>
<constructor-arg index=”3″ ref=”retryPolicy”></constructor-arg>
</bean>
<!– 调用 init 方法启动 –>
<bean id=”ZKCurator” class=”com.imooc.web.util.ZKCurator” init-method=”init”>
<constructor-arg index=”0″ ref=”client”></constructor-arg>
</bean>
4、上传或者删除 BGM 时调用 VideoServiceImpl.java 的方法
@Autowired
private ZKCurator zKCurator;
@Override
public void addBgm(Bgm bgm) {
String id = sid.nextShort();
bgm.setId(id);
bgmMapper.insert(bgm);
Map<String, String> map = new HashMap<>();
map.put(“operType”, BGMOperatorTypeEnum.ADD.type);
map.put(“path”, bgm.getPath());
zKCurator.sendBgmOperator(id, JSONUtils.toJSONString(map));
}
@Override
public void deleteBgm(String id) {
Bgm bgm = bgmMapper.selectByPrimaryKey(id);
bgmMapper.deleteByPrimaryKey(id);
Map<String, String> map = new HashMap<>();
map.put(“operType”, BGMOperatorTypeEnum.DELETE.type);
map.put(“path”, bgm.getPath());
zKCurator.sendBgmOperator(id, JSONUtils.toJSONString(map));
}
5、小程序编写代码监听 zookeeper 的节点,并对其做出相应的删除和上传操作【重点】
初始化 zookeeper 客户端
private CuratorFramework client = null;
final static Logger log = LoggerFactory.getLogger(ZKCuratorClient.class);
// public static final String ZOOKEEPER_SERVER = “120.79.18.36:2181”;
public void init() {
if(client != null) {
return;
}
// 重试策略
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 5);
// 创建 zk 客户端 120.79.18.36:2181
client = CuratorFrameworkFactory.builder().connectString(resourceConfig.getZookeeperServer()).sessionTimeoutMs(10000)
.retryPolicy(retryPolicy).namespace(“admin”).build();
// 启动客户端
client.start();
try {
addChildWatch(“/bgm”);
} catch (Exception e) {
e.printStackTrace();
}
}
监听 zk-server 的节点,当短视频后台管理系统上传或者删除某个 BGM 的时候,小程序后台服务器通过 Zookeeper 监听自动下载背景音乐
public void addChildWatch(String nodePath) throws Exception {
final PathChildrenCache cache = new PathChildrenCache(client, nodePath, true);
cache.start();
cache.getListenable().addListener(new PathChildrenCacheListener() {
@Override
public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception {
if(event.getType().equals(PathChildrenCacheEvent.Type.CHILD_ADDED)){
log.info(“ 监听到事件 CHILD_ADDED”);
//1. 从数据库查询 bgm 对象,获取路径 Path
String path = event.getData().getPath();
String operatorObjStr = new String(event.getData().getData());
Map<String, String> map = JsonUtils.jsonToPojo(operatorObjStr, Map.class);
String operatorType = map.get(“operType”);
String songPath = map.get(“path”);
// String[] arr = path.split(“/”);
// String bgmId = arr[arr.length-1];
// Bgm bgm = bgmService.queryBgmById(bgmId);
// if(bgm == null){
// return;
// }
//1.1 bgm 所在的相对路径
// String songPath = bgm.getPath();
//2. 定义保存到本地的 bgm 路径
// String filePath = “E:\\imooc_videos_dev” + songPath;
String filePath = resourceConfig.getFileSpace() + songPath;
//3. 定义下载的路径(播放 url)
String[] arrPath = songPath.split(“\\\\”); //windows
// String[] arrPath = songPath.split(“/”); //linux
String finalPath = “”;
//3.1 处理 url 的斜杠以及编码
for(int i=0; i<arrPath.length;i++){
if(StringUtils.isNotBlank(arrPath[i])) {
finalPath += “/”;
finalPath += URLEncoder.encode(arrPath[i], “UTF-8”);
}
}
// String bgmUrl = “http://192.168.199.150:8080/mvc” + finalPath;
String bgmUrl = resourceConfig.getBgmServer() + finalPath;
if(operatorType.equals(“1”)){
// 下载 bgm 到 springboot 服务器
URL url = new URL(bgmUrl);
File file = new File(filePath);
FileUtils.copyURLToFile(url, file);
client.delete().forPath(path);
}else if(operatorType.equals(“2”)){
File file = new File(filePath);
FileUtils.forceDelete(file);
client.delete().forPath(path);
}
}
}
});
}