前言:对于之前系统地学习java之路,现在面临找工作,发现对项目的询问比较多,之前只是把自己的项目挂在了github上,还是总结一下比较好。
一、需求分析
第一眼就看上了这个项目,觉得一个可以实现用户注册登录,上传音频章节信息图书资料的项目对我而言十分合适,其中有JDBC编程实现的注册登录上传书籍等静态文本内容数据库交互操作。又有上传音频播放这种比较新鲜我没有学习过的点。拿来做项目真是极好的。
进入正题:做朗读者网的需求重点是实现小说上传和音频的上传,用户的注册和登录。
之后可以增加其他的拓展性需求,比如加个视频,美化下界面,实现用户管理,个人中心什么的。
二、实施可行性
从需求分析也能得出,目前我的水平只有开发学生信息管理系统登录注册功能时MVC那一套后端的开发,这次的开发,我的技术难点在于从头开发,相当于前端那一套得研究一下,录制音频的方法也得好好学习一下子。
可以解决:用户注册登录,添加书籍章节。
技术难点:录制音频,上传音频,播放音频。前端页面的开发。
三、技术难点解决
结合菜鸟编程和csdn上的教程找到一个视频的录制上传。
record.html文件
`
预览
录制中
以及JavaScript
// document 是在浏览器中运行时一直存在的一个变量,表示的意思是代表文档树
// html Document Object Model Tree DOM 树
// document 粗糙的可以理解成这棵树的根
// getElementById 从树上,根据 id,找到对应的结点(标签)
let preview = document.getElementById("preview");
let recording = document.getElementById("recording");
let startButton = document.getElementById("startButton");
let stopButton = document.getElementById("stopButton");
let downloadButton = document.getElementById("downloadButton");
function wait(delayInMS) {
// setTimeout(执行什么方法,多少毫秒之后)
// 类似 Java 中的定时器(Timer)
// 设定一个闹钟一样的效果
return new Promise(resolve => setTimeout(resolve, delayInMS));
}
function startRecording(stream, lengthInMS) {
console.log("开始录制");
let recorder = new MediaRecorder(stream); // 定义一个媒体录制对象
let data = [];
// 当(on) 数据(data)可用(available) 时,执行该方法
recorder.ondataavailable = function (event) {
console.log("数据可用");
// event.data 录制下来的视频和音频数据,存入 data 数组
data.push(event.data); // 线性表的尾插
};
// 开始录制
recorder.start();
// resolve 成功的时候应该执行的方法,对应 then 传入的方法
// reject 失败的时候应该执行的方法,对应 catch 传入的方法
let stopped = new Promise(function (resolve, reject) {
recorder.onstop = resolve;
recorder.onerror = function(event) {
reject(event.name);
}
});
// 持续 lengthInMS 时间后,执行 then 中的方法
let recorded = wait(lengthInMS).then(
function() {
// 20 秒之后
// 判断 recorder 是否还在录制,如果还在录制 == "recording",则,停止录制
if (recorder.state == "recording") {
console.log("停止录制");
recorder.stop();
}
}
);
return Promise.all([
stopped,
recorded
])
.then(() => data);
}
function startCapturing() {
console.log("点击采集");
// 会触发,申请权限的操作
let promise = navigator.mediaDevices.getUserMedia({
video: true, // 申请摄像头权限
audio: true // 申请麦克风权限
});
// 如果用户同意了,就执行 then 中的方法,如果失败(用户不同意 or 其他失败)会执行 catch 中的方法
let promise2 = promise.then(function(stream) {
console.log("同意授权");
// 用户同意了
// stream 变量就代表录制的视频和音频了
preview.srcObject = stream;
// 处理兼容性的,类似 if (!preview.captureStream) { preview.captureStream = preview.mozCaptureStream; }
preview.captureStream = preview.captureStream || preview.mozCaptureStream;
// 接着执行的是,当 preview 开始(on) 播放(palying) 时,执行 then 的方法
// resolve 形参对应的实参就是 xxxx 函数
return new Promise(function(resolve) {
preview.onplaying = resolve;
});
});
function xxxx() {
return startRecording(preview.captureStream(), 5000); //
}
promise2.then(xxxx) // 调用 function(resolve) { ... } 这个函数
.then(function (data) {
console.log("使用录制下来的数据");
console.log(data);
let recordedBlob = new Blob(data, {
type: "video/webm" });
recording.src = URL.createObjectURL(recordedBlob);
})
.catch(e => {
console.log(e);
});
}
function stopRecording() {
console.log("点击了结束录制");
}
// startButton.addEventListener("click", startRecording); <-- 和下面的写法,目前可以认为是一样的效果
startButton.onclick = startCapturing;
// 进行事件绑定,发生了 startButton 的点击(click)事件后,
// 请执行 startRecording
// 这种形态就是俗称的回调函数(callback)
// 当 startButton 上有了 click 事件时,startButton.onclick();
stopButton.onclick = stopRecording;
// 在 stopButton 发生了(on) 点击(click)事件后,执行 stopRecording 函数
弄出来效果是这样的。到时候只保留音频录制功能就好了。
四:ER图,数据库设计
一个用户可以上传多部小说(1:n)
一个小说可以有多个章节(1:n)
一个章节对应一个音频(1:1)
ER图大概就是:
用户 —> 小说 —>章节 —> 音频
然后设置表的属性:
Users:uid,username,password
Books:bid,uid,tittle
Sections:sid,bid,name
Audios:aid,sid,uuid,type,content
uuid是逻辑主键,好处是如果audios和别的表合并那aid可能就失效了,但是uuid是独立的主键,永远可用正确。这里就用用。
五、执行建库建表代码和xml文件
create database Audio charset utf8mb4;
use Audio;
create table users (
uid int primary key auto_increment comment '用户id',
username varchar(64) not null unique comment '用户名',
password char(64) not null comment '经过sha-256计算后的用户密码'
);
create table books (
bid int primary key auto_increment comment '小说id',
uid int not null comment '上传用户id',
title varchar(100) not null comment '小说名称'
);
create table sections (
sid int primary key auto_increment comment '章节id',
bid int not null comment '属于哪本小说的id',
name varchar(100) comment '章节名称'
);
create table audios (
aid int primary key auto_increment comment '音频id',
sid int not null unique comment '属于哪个章节的id',
uuid char(36) not null comment 'uuid音频的唯一标识',
type varchar(20) not null comment '音频类型audio/wmv audio/mp3',
content longblob default null comment '音频内容'
);
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- 项目基本信息 -->
<groupId>zjw</groupId>
<artifactId>AudioFiction</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- war 包形式打包,IDEA会根据这个选项,自动创建 artifacts 选项-->
<packaging>war</packaging>
<!-- 项目的字符集 + 字符集选项配置 -->
<properties>
<encoding>utf-8</encoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<!-- 添加需要的第三方依赖jar包 -->
<dependencies>
<!-- 提供 Servlet 提供的标准接口 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<!-- 这个 jar 包只在开发 + 编译阶段使用,运行阶段不需要 -->
<scope>provided</scope>
</dependency>
<!-- 添加 MySQL Driver 的依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!-- 添加处理 json 数据的依赖 -->
<dependency>
<groupI