超低配版ssm项目
第一次写项目文档,非常地不成熟,格式都是随心所欲的。。。
分享记录一下
项目文档
一、需求分析
1、客户
- 注册、登录、退出
- 通过曲名、艺术家、专辑来搜索
- 直接购买音乐
- 查看自己购买的音乐
- 能收藏自己喜欢的音乐
- 能加入购物车,购物车能删除、添加操作
- 音乐主页有用户评论的信息,可以发表自己的评价
- 用户支付完后可以下载音乐实体
- 进行各种排序,默认、价格、热度排序(销量*70%+收藏量 *30% 的占比)
2、游客
- 查看、搜索
- 使用特定功能需要提示登录
3、管理员
- 查看已经注册的用户
- 上架、下架音乐
- 查看修改曲子详情(包括价格、销量等)
- 修改专辑信息
- 增加专辑信息
- 查看订单
- 能对用户进行充值、重置密码操作
二、技术选型
(具体版本号请查看Meaven中的依赖信息)
前端:主要有html、css(少量js),加上jsp
后台:SSM框架
数据库 :Mysql
三、数据库表设计(music_shop)
1、用户表user
- id id为0是游客
- 登录账号 account
- 登录密码 password
- 昵称 nickname
- 余额 money
2、音乐表song
- id
- 曲名 name
- 艺术家 artist
- 所属专辑(通过id链接)album_id (如果为0,代表没有所属专辑)
- 专辑内编号 number_in_album
- 年份 issue_date
- 价格 price
- 音乐实体链接地址 entity_address
- 伪删除列 is_delete
(销量从订单详情表中联合得到)
3、专辑表album
- id
- 专辑名称 name
- 专辑所属艺术家 artist(如果没有这列,专辑与歌曲严重耦合)
- 专辑简介 introduction
- 发行年份 issue_date
4、收藏表favor(舍弃)
-
id
-
用户id user_id
-
音乐id song_id
5、点赞表(改为收藏表)like
- id
- 用户id user_id
- 音乐id song_id
6、评论表comment
- id
- 用户id user_id
- 音乐id song_id
- 内容 content
- 评论时间 comment_date
7、购物车表shopping_cart
- id
- 用户id user_id
- 音乐id song_id
用户从购物车清除后,该表相关信息会删除,并转移到订单表中。
当商品信息发生改变时,该表也要改变,包括删除。
8、订单表order(舍弃)
(一旦有记录就代表购买成功,不设置状态)
(和购物车表高度联合)
- id
- 用户id user_id
- 订单价值 price (可以从订单详情表中聚合得到,但为了方便就多设一个字段)
- 创建时间 create_time
9、订单详情表order_details(舍弃)
- id
- 订单id(可大量重复)order_id
- 音乐id song_id
- 单首音乐价格 song_price(不能联表查,要记录当时购买时价格)
10、管理员表manager
- id
- 登录名 account
- 登录密码 password
11、购买记录表purchase_record
由于song表中设置了删除状态栏,所以歌曲信息可以联表查
- id
- 用户id user_id
- 音乐id song_id
- 单首音乐价格 purchase_price(不能联表查,要记录当时购买时价格)
- 创建时间 create_time
四、项目目录
1、com.hao.config
存放监听器,拦截器
- CommonInterceptor 拦截器
- UserNumberListener 监听器,统计用户数量
2、com.hao.controller
存放控制器
- UserController 处理有关用户方面的请求
- SongController 处理有关歌曲方面的请求
- ShoppingCartController 处理有关购物车方面的请求
- PurchaseController 处理有关购买方面的请求
- ManageController 处理理员端的请求
- CommonController 处理一些不好分类的请求
- CommentController 处理有关评论方面的请求
- AlbumController 处理有关专辑方面的请求
3、com.hao.mapper
存放dao对象,以及Mybatis绑定的xml文件
- AlbumMapper 处理有关专辑表的数据
- CommentMapper 处理有关评论表的数据
- LikeMapper 处理有关收藏表的数据
- ManagerMapper 处理有关管理员表的数据
- PurchaseRecordMapper 处理有关购买记录表的数据
- ShoppingCartMapper 处理有关购物车表的数据
- SongMapper 处理有关歌曲表的数据
- UserMapper 处理有关用户表的数据
- TestMapper 测试用
4、com.hao.model
存放项目模型对象
- Album 专辑模型
- Comment 评论模型
- Manager 管理员模型
- PurchaseRecord 购买记录模型
- ShoppingCart 购物车模型
- Song 歌曲模型
- User 用户模型
5、com.hao.service
存放服务接口,以及接口的实现类
- AlbumService 专辑服务对象
- CommentService 评论服务对象
- LikeService 收藏服务对象
- ManagerService 管理服务对象
- PurchaseService 购买记录服务对象
- ShoppingCartService 购物车服务对象
- SongService 歌曲服务对象
- UserService 用户服务对象
6、resources目录
存放相关配置文件
- application.xml Spring总配置,包括MVC,Service,Dao的配置
- database.properties 数据库配置
- mybatis.xml Mybatis配置
7、web/static sources目录
存放静态资源文件,包括图片,css样式文件等
- css和fonts目录 存放awesome的样式库
- img 存放静态图片
8、web/WEB-INF/JSP目录
存放用户端相关的页面
9、web/WEB-INF/JSP/manager目录
存放管理员端相关的页面
五、功能实现思路
其实很多实现思路基本都一样,用户发送请求(包含特定参数)–> 控制器处理,调用相应服务 --> 把CURD的结果告知给用户。
1、用户方面功能
登录
-
思路:前端发送数据,后台将数据和数据库中的数据进行匹配,成功,将用户信息注入到当前会话,进入到主页;失败则返回登录页,并给出提示信息。
-
相关接口:/user/login
-
控制器方法:UserController.toLoginPage
UserController.login
-
服务方法:UserService.isMatch
-
数据访问层方法:UserMapper.isMatch
注册
-
思路:前端发送注册相关的表单信息(已经验证过格式),后台将注册的信息进行再次验证,包括再次输入密码是否相对应,账号是否已经被注册过。信息验证成功后,将用户注册的信息持久化到数据库;失败,则返回提示信息给JSP进行显示
-
相关接口:/user/register
-
控制器方法:UserController.toRegisterPage
UserController.register
-
服务方法:UserService.register
-
数据访问层方法:UserMapper.findUserByAccount
UserMapper.insert
退出登录
- 思路:后台收到请求后,将会话中的用户属性移除即可
- 相关接口:/quitLogin
- 控制器方法:CommonController.quitLogin
- 服务方法:无
- 数据访问层方法:无
2、购物车方面的功能
查看购物车
-
思路:控制器拿到相应的对象列表,填充到视图对象,jsp渲染即可。
-
相关接口:/shoppingCart/details/{用户id}
-
控制器方法:ShoppingCartController.toDetails
-
服务方法:shoppingCartService.getShoppingCartByUserID
-
数据访问层方法:shoppingCartMapper.getShoppingCartByUserID
shoppingCartMapper.countPrice
-
sql写法:在countPrice中,用了一个聚合查询
添加购物车
-
思路:前端送来用户id和歌曲id,控制器将根据这些数据持久化到数据库的购物车表即可
-
相关接口:/shoppingCartadd/{userID}/{songID}
-
控制器方法:ShoppingCartController.add
-
服务方法:shoppingCartService.add
-
数据访问层方法:shoppingCartMapper.ifExist判断是否添加过
purchaseRecordMapper.ifExist判断是否购买过
shoppingCartMapper.add持久化
删除购物车
- 思路:前端送来用户id和歌曲id,控制器可以根据这些数据将数据库中相关记录删去。
- 相关接口:/shoppingCart/delete/{userID}/{songID}
- 控制器方法:ShoppingCartController.delete
- 服务方法:shoppingCartService.delete
- 数据访问层方法:shoppingCartMapper.delete
3、购买方面的功能
购买歌曲
-
思路:前端送来要购买的歌曲id,控制器从会话中拿到用户相关信息,如果余额充足,则直接购买成功,返回到已经购买的歌曲界面;如果余额不足,或已经购买过,返回提示信息到原页面。(本应该在这里增加一个业务的)
-
相关接口:/purchase/buy/{songID}
-
控制器方法:PurchaseController.purchase
-
服务方法:purchaseService.buy
-
数据访问层方法:purchaseRecordMapper.ifExist判断是否购买过
songMapper.findSongByID查找要购买的歌曲
userMapper.updateMoney更新数据库中用户
purchaseRecordMapper.insert插入购买记录
展示用户购买记录
- 思路:控制器拿到相应的对象列表,填充到视图对象,jsp渲染即可。
- 相关接口:/purchase/show/{userID}
- 控制器方法:PurchaseController.show
- 服务方法:purchaseService.getListByUserID
- 数据访问层方法:purchaseRecordMapper.getListByUserID
对购物车进行结算
-
思路:根据前端送来的用户id,查找到与该用户对应的购物车,如果购物车为空或者余额不足,返回提示信息;否则,直接把购物车中的歌曲列表中的歌曲全部插入到购买记录表中。
-
相关接口:
-
控制器方法:PurchaseController.clearShoppingCart
-
服务方法:PurchaseService.buyThroughShoppingCart
-
数据访问层方法:shoppingCartMapper.countPrice计算购物车价值
shoppingCartMapper.getShoppingCartByUserID拿到购物车实 体
shoppingCartMapper.clearByUserID把购物车表与该用户id相同 的记录除去
userMapper.updateMoney更新用户数据库中的余额
4、评论方面的功能
发表评论
- 思路:前端送来用户id与要评论的歌曲id,后台根据两个id及表单中的内容,将该条评论记录持久化到数据库
- 相关接口:/comment/addComment/{userID}/{songID}
- 控制器方法:CommentController.addComment
- 服务方法:commentService.addComment
- 数据访问层方法:commentMapper.addComment
展示评论
- 思路:和展示歌曲的页面嵌套,在展示歌曲的同时把评论列表打印出来。控制器拿到相应的对象列表,填充到视图对象,jsp渲染即可。
- 相关接口:/song/showDetails/{songID}
- 控制器方法:SongController.showDetails
- 服务方法:commentService.getList
- 数据访问层方法:commentMapper.getList
5、专辑方面的功能
展示所有专辑
- 思路:在用户请求去专辑首页的时候,控制器拿到相应的对象列表,填充到视图对象,jsp渲染即可。
- 相关接口:/album/homePage
- 控制器方法:AlbumController.toAlbumHomePage
- 服务方法:albumService.getList
- 数据访问层方法:albumMapper.getList
展示特定专辑的功能
- 思路:前端送来要展示的专辑id,控制器查找到特定的专辑后,填充到视图模型,jsp渲染即可。
- 相关接口:/album/details/{albumID}
- 控制器方法:AlbumController.showAlbumDetails
- 服务方法:albumService.getAlbumByID
- 数据访问层方法:albumMapper.getAlbumByID
6、收藏方面的功能
查看收藏
- 思路:根据前端送来的用户id,控制器拿到相应的对象列表,填充到视图对象,jsp渲染即可。
- 相关接口:/song/showLikeDetails/{userID}
- 控制器方法:SongController.showLikeDetails
- 服务方法:likeService.getLikeListByUserID
- 数据访问层方法:likeMapper.getLikeListByUserID
收藏某一歌曲
-
思路:后台根据歌曲id与用户id,判断当前用户是否收藏过当前歌曲,如果收藏过,则取消收藏;如果没有收藏过,则把相应数据持久化到数据库中。
-
相关接口:/song/showDetails/likeSong/{songID}
-
控制器方法:SongController.likeSong
-
服务方法:likeService.isUserLike判断用户是否已经收藏过
likeService.cancelLike取消收藏
likeService.likeSong收藏
-
数据访问层方法:likeMapper.isUserLike判断用户是否已经收藏过
likeMapper.cancelLike取消收藏
likeMapper.likeSong收藏
7、游客方面的功能
查看、搜索
- 思路:由于进入到主页、搜索功能,不用与特定的用户进行绑定,所以可以直接把没有登录的游客直接放行到主页中,并可以使用搜索功能
- 像正常用户那样,调用查看、搜索的接口即可。
提示登录
- 思路:jsp中,用if标签对会话进行判断,如果会话中有用户属性,则是已经登录;如果会话中没有用户属性,则说明没有进行登录,将含有请求的版块用含有提示的版块替代
8、歌曲方面功能
主页展示歌曲、专辑
-
思路:展示歌曲时,在用户请求进入到主页的时候,控制器根据URL中的参数,来调用不同的查找歌曲的服务,如1代表价格排序,2代表热度排序,将得到的歌曲列表填充到视图模型中,交给Jsp渲染。其中,歌曲排序功能的实现主要依靠sql语句中的order by关键字进行排序。
-
相关接口:/song/homePage/{select}(select参数代表歌曲将以哪种排序形式呈现给用户)
-
控制器方法:SongController.toHomePage
-
服务方法:songService.getListByDefault默认排序
songService.getListByPrice价格排序
songService.getListByHot热度排序
-
数据访问层方法:songMapper.getListByDefault
songMapper.getListByPrice
songMapper.getListByHot
-
sql语句写法:在查询的时候用到了聚合查询。查找歌曲列表的sql语句如下所示(以查询默认排序的歌曲列表为例):
<resultMap id="song" type="Song"> <id column="id" jdbcType="INTEGER" property="id"/> <result column="name" jdbcType="VARCHAR" property="name"/> <result column="artist" jdbcType="VARCHAR" property="artist"/> <result column="album_id" jdbcType="INTEGER" property="albumID"/> <!--有重复的字段,要起别名,并且在column属性中用别名来指定--> <result column="albumName" jdbcType="VARCHAR" property="albumName"/> <result column="number_in_album" jdbcType="INTEGER" property="numberInAlbum"/> <result column="issue_date" jdbcType="DATE" property="issueDate"/> <result column="price" jdbcType="FLOAT" property="price"/> <result column="entity_address" jdbcType="VARCHAR" property="entityAddress"/> <result column="is_delete" jdbcType="INTEGER" property="isDelete"/> <result column="COUNT(o.`id`)" jdbcType="INTEGER" property="sale"/> <result column="COUNT(l.`id`)" jdbcType="INTEGER" property="like"/> </resultMap> <select id="getListByDefault" resultMap="song"> SELECT s.`id`,s.`name`,s.`artist`, `album_id`,a.`name` albumName,`number_in_album`, s.`issue_date`,`price`,`entity_address`,`is_delete`, COUNT(o.`id`),COUNT(l.`id`)<!--两个count函数分别统计销量和收藏量--> FROM `song` s <!--查找歌曲表--> LEFT JOIN `album` a <!--左联表查询,以歌曲表为主--> ON s.`album_id`=a.`id`<!--条件是歌曲表中的专辑id与专辑的id相同--> LEFT JOIN `purchase_record` o<!--得到的结果再与购买记录表左联查询。联结的目的是为了聚合查询统计歌曲的销量--> ON s.`id`=o.`song_id`<!--条件是歌曲id与购买记录表中的歌曲id一致---> LEFT JOIN `like` l<!--再与收藏表左联。联结的目的是为了聚合查询统计歌曲的总的收藏数--> ON s.`id`=l.`song_id`<!--条件是歌曲id与收藏表中的歌曲id一致--> WHERE `is_delete`=0<!--除去已经删除的歌曲--> GROUP BY s.`id`<!--以歌曲的id为一组--> ORDER BY s.`id`;<!--并以歌曲id进行默认排序--> </select>
通过曲名、艺术家、专辑搜索
-
思路:比较简单,和主页展示歌曲的思路一样,唯一不同的是数据库查询的方式不同,在查找到的歌曲列表中,加上like和or关键字即可实现。关键字从前端的表单中获得。
-
相关接口:/song//homePage/findSongsByKey
-
控制器方法:SongController.findSongsByKey
-
服务方法:songService.findSongsByKey
-
数据访问层方法:songMapper.findSongsByKey
-
sql写法:
HAVING [歌曲名栏目] like [查找的关键字] OR [艺术家栏目] like [查找的关键字] OR [专辑名栏目]like [查找的关键字];
9、管理员方面功能
登录
-
思路:对输入的登录信息进行验证,成功进入到管理主页;失败返回提示信息。
-
相关接口:/manage//login
-
控制器方法:ManageController.toLogin GET方法,去到登录页面
ManageController. login POST方法,提交表单
-
服务方法:managerService.isMatch
-
数据访问层方法:managerMapper.isMatch
展示可管理的歌曲
- 思路:控制器拿到相应的对象列表,填充到视图对象,jsp渲染即可。
- 相关接口:/manage/manageSong
- 控制器方法:ManageController.manageSong
- 服务方法:songService.getListByDefault
- 数据访问层方法:songMapper.getListByDefault
修改歌曲
-
思路:在用户请求去修改歌曲页面的时候,传入歌曲id,控制根据id查找到特定的歌曲对象,并且要找到所有专辑的列表(因为用户可能要修改歌曲所属的专辑,专辑列表呈现给用户进行选择),交给jsp渲染。jsp渲染的目的是展示给用户原本的歌曲信息,方便进行对比修改。修改时,根据表单的歌曲信息,更新数据库记录即可
-
相关接口:/manage/modifySong/{songID} GET方法得到页面
/manage/toModifySong/{songID} POST方法提交表单
-
控制器方法:ManageController.toModifySongPage 得到修改页面
ManageController.modifySong 修改歌曲
-
服务方法:songService.findSongByID 拿到歌曲的实体对象
albumService.getList 拿到所有专辑的列表
songService.updateSong 更新歌曲
-
数据访问层方法:songMapper.findSongByID拿到原先的歌曲对象
albumMapper.getList 拿到专辑列表
songMapper.updateSong更新完原歌曲对象属性后,更新到数据库
删除歌曲
- 思路:因为歌曲表中有删除标志位,所以直接把表中标志位栏目置为1即可。
- 相关接口:/manage/delete/{songID}
- 控制器方法:ManageController.delete
- 服务方法:songService.deleteSong
- 数据访问层方法:songMapper.deleteSong
添加歌曲
-
思路:因为添加歌曲的时候,可能要选择专辑,所以控制器 应该要把专辑列表放入视图中,jsp渲染给用户进行选择。添加时,后台创建一个歌曲对象,根据前端表单信息填充属性,然后持久化到数据库即可。
-
相关接口:/manage/addSong GET方法得到页面
/manage/addSong POST方法提交表单
-
控制器方法:ManageController.toAddSongPage 得到添加歌曲页面
ManageController.addSong 添加歌曲
-
服务方法:albumService.getList 得到专辑列表
songService.addSong 添加歌曲
-
数据访问层方法:albumMapper.getList 得到专辑列表
songMapper.insertSong 插入歌曲
songMapper.getLatestSongID 得到插入歌曲的id
展示可管理的专辑
- 思路:控制器拿到相应的对象列表,填充到视图对象,jsp渲染即可。
- 相关接口:/manage/manageAlbum
- 控制器方法:ManageController.manageAlbum
- 服务方法:albumService.getList
- 数据访问层方法:albumMapper.getList
添加专辑
-
思路:因为添加专辑的时候,要选择专辑内的歌曲,所以在去到添加专辑页面的时候,应该先得到歌曲列表,然后填充到视图中去。添加时,新建一个专辑对象,根据前端的表单信息填充对象属性,然后持久化到数据库
-
相关接口:/manage/addAlbum GET方法得到添加专辑的页面
/manage/addAlbum POST方法添加专辑
-
控制器方法:ManageController.toAddAlbumPage 得到添加专辑页面
ManageController.addAlbum 添加专辑
-
服务方法:songService.getListByDefault 拿到歌曲列表
albumService.addAlbum 添加专辑
-
数据访问层方法:songMapper.getListByDefault 拿到歌曲列表
albumMapper.addAlbum 持久化专辑对象
albumMapper.getLatestAlbumID 得到刚刚持久化的专辑对象id
songMapper.updateSongAlbumID 更新新添加入的歌曲的专辑id
修改专辑
-
思路:修改专辑的时候,同样需要一个歌曲列表,供用户进行选择添加入专辑。所以要把专辑列表添加入视图对象中。为了方便给用户进行对比修改,需要给用户呈现原先的专辑信息。修改时,根据专辑id来修改专辑,根据前端的表单信息更新原专辑对象即可。如果有添加歌曲,那么还要更新要添加的歌曲的专辑id
-
相关接口:/manage/modifyAlbum/{albumID} GET方法得到修改专辑页面
/manage/modifyAlbum/{albumID} POST提交表单
-
控制器方法:ManageController.toModifyAlbumPage 得到修改专辑页面
ManageController.modifyAlbum 修改专辑
-
服务方法: albumService.getAlbumByID 拿到要修改的专辑实体
songService.getListByDefault拿到歌曲列表
albumService.updateAlbum 更新专辑
-
数据访问层方法:albumMapper.getAlbumByID 拿到专辑
songMapper.getListByDefault 拿到歌曲列表
songMapper.updateSongAlbumID 更新添加的歌曲的专辑id
albumMapper.updateAlbum 更新专辑
删除专辑
-
思路:首先把专辑的歌曲列表遍历,把每个歌曲的专辑id置为0(代表没有专辑),然后把专辑直接从表中删除
-
相关接口:/manage/deleteAlbum/{albumID}
-
控制器方法:ManageController.deleteAlbum
-
服务方法:albumService.deleteAlbum
-
数据访问层方法:albumMapper.getAlbumByID 拿到专辑实体
songMapper.updateSongAlbumID 更新歌曲专辑id
albumMapper.deleteAlbum删除专辑
展示可管理用户
- 思路:拿到所有用户的列表,填充到视图对象中,jsp渲染即可。
- 相关接口:/manage/manageUser
- 控制器方法:ManageController.toManageUserPage
- 服务方法:serService.getList
- 数据访问层方法:userMapper.getList
重置用户密码
- 思路:直接根据前端送来的用户id,更新数据库的密码栏目
- 相关接口:/manage/resetPassword/{userID}
- 控制器方法:ManageController.resetPassword
- 服务方法:userService.resetPassword
- 数据访问层方法:userMapper.updatePassword
增加用户余额
-
思路:直接根据前端送来的用户id,更新数据库的余额栏目
-
相关接口:/manage/addMoney/{userID}
-
控制器方法:ManageController.addMoney
-
服务方法:userService.addMoney
-
数据访问层方法:userMapper.findUserByID 找到原用户对象
userMapper.updateMoney 更新余额
10、项目方面功能
统计在线人数
- 思路:在tomcat配置中(web.xml)注册一个会话监听器,当有会话创建的时候,在web容器的应用上下文中拿到关于人数数量的属性onlineNumber(Integer类型,如果为空,则新建一个),令onlineNumber数量加1,放回到上下文中;如果有会话销毁,则相同方法,使onlineNumber数量减1,放回上下文中;jsp拿到应用上下文的onlineNumber属性,打印即可
- 作用类:com.hao.UserNumberListener
防止用户乱入管理界面
- 思路:在spring配置中注册一个拦截器,如果URL请求中含有login,说明要去登录,放行;如果会话中有管理员属性,说明登录过,放行;否则强制用户重定向到登录页面
- 作用类:com.hao.CommonInterceptor
六、目前存在的问题
因为开发时间有点赶(刚好遇到我数据结构课设答辩),很快就到了考核任务提交时间,然后要提交前又发现一些问题,只能先记录下来了,其它的已经尽量改了。
-
**问题:**收藏功能时,刷新会再次发送收藏请求,导致收藏会取消
**解决思路:**因为采用了请求转发,所以出现这个问题,应该用重定向,并用上spring的flash属性功能(因为重定向后数据会消失)
-
问题:管理员那边如果对用户进行操作(比如加钱),会话中的用户体不会更新
**解决思路:**会话中应该就放用户id作为登录标志就好了(原本是把整个用户对象丢进去),但前期设计时太多直接使用了会话中的用户体的属性,要改起来工作量大。
-
**问题:**后台输入信息没有表单验证
**解决思路:**前端设计验证,或者后台再进行条件判断
-
**问题:**音乐mp3格式文件下载功能没有实现
**解决思路:**实现起来也很简单,用户点击下载时,设置一个拦截器,如果判断用户已经购买,则放行,给出资源;如果用户没有购买,则拦截。
-
**问题:**如果先加入购物车,再直接购买,然后再进入购物车结算,发现一首歌可以购买两次,之后判断是否已经购买时,就会出现查询错误
**解决思路:**购买商品时,检查购物车表,或者结算购物车时,把已经购买的商品不要进行结算。
-
**问题:**1个账号可以同时登录,导致信息会异步
**解决思路:**无