博客简介
主要内容:基于 web 技术实现一个有声小说项目
具体包括:项目背景、项目介绍、具体流程、实现代码、结果分析
项目实现
一、立项
项目名:AF有声小说
为了验证学习成果、印证知识点的掌握程度,模仿 “喜马拉雅FM” 实现一个有声小说项目,实现其部分功能。主要模仿用户对有声小说类软件的基础功能需求,目的是实现用户的注册、登录、对书籍的管理与对音频的操作等,基于Java语言的特性和组件开发的特点,预留了其他的功能实现接口留待日后补充,CSS样式开发尚未学习,留待日后补充。
二:需求分析
核心需求:在浏览器上实现小说的上传与音频的上传
可拓展需求:增加其他的可提升可用性的功能,如收藏、下载等;提升工作性能,比如减小音频的播放等待时间、压缩文件大小等;增强美观性,比如对声音进行柔化处理、对界面进行美化处理等
项目的边界:

讯享网
核心功能:书籍、章节与音频的管理
必要辅助功能:用户管理(注册、登录、注销等)
暂不实现:功能、性能、美观等,这部分非必要功能此后继续慢慢补充
三、可行性评估
需要完成的主要功能有:
1、用户注册
2、用户登录
3、添加书籍
4、添加章节
5、录制各章节的对应音频
6、上传音频
7、音频播放

四、技术预研
如何在浏览器上实现音频的播放、如何在浏览器上实现音频的录制与上传
通过学习官网上的实例代码,模仿使用视频的录制上传
学习用record.html:
<meta charset="utf-8"> <style> .button {
border: solid 1px black; padding: 12px; width: 120px; height: 50px; line-height: 50px; text-align: center; } </style> <div class="left"> <div id="startButton" class="button">开始录制</div> <h2>预览</h2> <!-- autoplay 自动播放 --> <!-- muted 静音 --> <video id="preview" width="160" height="120" controls muted></video> </div> <div class="right"> <div id="stopButton" class="button">停止</div> <h2>录制中</h2> <!-- controls 显示播放器的控制面板(播放、暂停、停止)--> <video id="recording" width="160" height="120" controls></video> <div id="downloadButton" class="button">下载</div> </div> <script src="record.js" charset="utf-8"></script>
讯享网
学习用record.js:
讯享网console.log("OK"); // 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 函数
实现效果:
初始页面:

点击了开始录制:

允许开启摄像头:

其余部分尝试后从控制台观察,可以实现功能无错误,预研结束

五、数据建模
需要的数据模型:用户Users、书本Books、章节Sections、音频Audios
数据关系的简易E-R图:

各模型属性:
Users:uid(用户id)、username(用户名)、password(密码)
Boods:bid(小说id)、uid(上传用户id)、title(小说名称)
Sections:sid(章节id)、bid(所属小说id)、name(章节名)
Audios:aid(音频id)、sid(对应章节id)、uuid(唯一标识码)、 type(音频类型)、content(音频内容)
六、代码实现
1、建库、建表:实现代码如下
create database AudioFiction charset utf8mb4; use AudioFiction; 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 '音频内容' );
2、准备开发环境:配置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> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.62</version> </dependency> </dependencies> </project>
3、需求方法列表:根据需求写出请求方法,URL
需求如下:
1、匿名用户,可以查看所有书的列表,选择出想听的书
2、已登录用户,可以上传一本书且被其他用户看到
3、书的上传者,可以录入新的章节且被其它用户看到
4、书的上传者,可以为某个章节录入音频且被其它用户看到
5、匿名用户,可以选择一本想听的书,看到书的章节列表
6、匿名用户,可以选择某一章节,播放该章节的对应音频
7、用户管理

如果只需要从服务器获取数据——GET
如果需要向服务器提供数据——POST
4、代码架构设计: MVC模式
MVC模式:Model-View-Controller,模型、视图、控制器框架。M(Model)封装应用程序的数据结构和事务逻辑,集中体现应用程序的状态,当数据改变的时候能够在视图里体现出来;V(View)是Model的外在表现,当模型改变时有所改变;C(Controller)对用户的输入进行响应,将模型和视图联系到一起,负责将数据写到模型中,并调用视图

Servlet:处理接入逻辑
Service:统一的对象处理方法,数据加工
DAO:数据访问对象,负责对数据库进行操作
MySQL:数据存储仓库,用来存储输入的数据和运行时数据
5、编写代码: 创建好各传输层包,开始按照框架开发

创建包结构如图,将Java类按结构分类管理,有助于管理和修改
这里按照各包中的类统一罗列,实际编写时按照对象编写,例如一次性处理完 User 所有的相关类,包括 Model(User)、DAO(UserDao)、Service(UserService)、Servlet(UserLoginServlet、UserRegisterServler),将 User 对象的相关类在各个包中分别编写完成再对其进行测试,确认无误就开始下一个对象的编写直到全部完成
执行逻辑图:

Model:一般来说,这类对象都要重写 toString(),equals(),hashCode()方法
//初始化User对象 package zjw.Model; import java.util.Objects; public class User {
public int uid; public String username; public User() {
} public User(int uid, String username) {
this.uid = uid; this.username = username; } @Override public String toString() {
return "User{" + "uid=" + uid + ", username='" + username + '\'' + '}'; } @Override public boolean equals(Object o) {
if (this == o) return true; if (<
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/126436.html