本设计包含了 多人在线聊天室,微信扫码支付,在线签字,PDF合同在线生成,商户评分,基本的商铺信息管理,合同管理,新闻和公告管理,用户管理等功能,具体功能请参看论文中截图
在我国城市化高速发展的今天,各种规模的商城相继出现。商城需要招商,进驻商家进行经营,商城的商铺需要出租和管理,商城各经营场所的招商信息,公告信息和活动信息的发布,进驻商家的各种信息的管理,都是人工手动进行管理的,既没有效率又容易出错,查找起来更是相当的麻烦。基于上述原因,需要借助于一款基于Web的商城商户信息管理系统,便于商铺的在线出租和对商城,商家,商铺的信息的查询与管理,有利于营造良好的商城营商环境,减轻商城管理人员的工作强度,因此设计一款基于Web的商城商户信息管理系统具有较好的实用价值。
IntelliJ IDEA:是java编程语言开发的集成环境。在业界被公认为最好的java开发工具,尤其在智能代码助手、代码自动提示、重构、J2EE支持、各类版本工具(git、svn等)、JUnit、CVS整合、代码分析、 创新的GUI设计等方面的功能可以说是非常优秀的。
SSM框架:由Spring,SpringMVC,MyBatis组成的框架集,是一个优秀的web项目框架,是继SSH之后,目前比较主流的Java EE企业级框架。Spring的目的就是用来替代更加重量级的的企业级Java技术,用来简化java的开发难度。SpringMVC是在Spring框架内置的MVC实现的一个子框架,可以轻松的实现MVC架构的程序设计。MyBatis 是一个持久层框架,他可以灵活的书写SQL语句,他让我们省略了大部分的JDBC代码、设置参数和获取结果集的代码。只使用简单的XML 和注解来映射基本数据类型、Map 接口和POJO 到数据库记录。
Maven:基于项目对象模型(POM project object model)的一个强大的项目管理工具,可以通过pom.xml文件配置智能的管理jar包
WebSocket:是一种在单个TCP连接上进行全双工通信的协议,允许服务端主动向客户端推送数据。本设计使用WebSocket技术实现聊天大厅的功能。
Druid连接池:阿里巴巴开源平台上的一个项目,能够提供强大的监控和扩展功能,可以替换DBCP和C3P0连接池,提供了一个高效、功能强大、可扩展性好的数据库连接池。
2 需求分析
2.1 可行性分析
2.1.1 技术可行性
SSM框架是spring MVC,spring和mabatis框架的整合,是标准的MVC模式,将整个系统划分为表现层,controller层,service层,DAO层四层,是一个成熟的企业级应用开发框架,而且在性能方面,要优于大部分编程语言,跨平台性更是实现了一次编译,到处运行。
2.1.2 经济可行性
传统模式的店铺租赁,需要提前查看选定商铺,然后联系出租方,约定时间后,需要双方同时到达同一个地方,仔细阅读合同后,进行签字并支付现金。然后管理者手动进行记录,这种方式既费时又费力。将挑选店铺,签订合同,支付等集合在一起的本系统,可以省去这些麻烦,并且管理者可以更好的进行管理。
2.1.3 操作可行性
系统使用常见的,用户熟悉的友好界面,使用常见的编辑框,按钮等组件,不仅界面美观,而且容易操作,只要是会操作电脑即可使用。本系统简化了许多的操作逻辑,大部分操作只需要点击鼠标即可操作完成。
2.2 流程分析
图3-1 用户流程图
图3-2 管理员流程图
基于Web的商城商户信息管理系统,采用MVC设计模式,整体使用Maven管理项目,后端使用SSM框架(Spring,SpringMVC,Mybits),前端使用HTML5,Layui,jQuery,CSS3,聊天大厅使用WebSocket技术,数据库使用MySQL配合阿里的Druid连接池,使用Spring声明式事务管理,设计并实现的一个集商铺在线出租,商铺搜索,合同在线手写签字,合同在线生成,在线支付,聊天大厅,入驻商家展示评分,用户管理,商家管理,商铺管理,首页Banner管理,商城公告活动的发布和管理,合同审核等功能的系统。
基于Web的商城商户信息管理系统主要分为三个部分
1.管理部分:超级管理员对管理员的管理,管理员对用户的管理,管理员对商铺的管理,管理员对商家的管理,管理员对合同的管理,管理员对活动和公告的管理。
2.用户部分:用户查看商铺,用户租赁商铺,用户签订合同,用户对租赁的商铺的管理,用户之间的聊天
3.顾客部分:顾客可以对商家进行评论留言和评分,可以浏览店铺排行榜
图2-1 系统功能图
图2-2 用户E-R图
图2-3 管理员E-R图
图2-4 商铺E-R图
图2-5 公告E-R图
图2-6 合同E-R图
图2-7 总E-R图
列 名 | 字段名称 | 类型 | 主键 | 可否为空 |
uid | 用户ID | INT(32) | 是 | 否 |
username | 用户名 | VARCHAR(32) | 否 | 否 |
password | 用户密码 | VARCHAR(32) | 否 | 否 |
nickname | 用户昵称 | VARCHAR(32) | 否 | 是 |
telephone | 手机号 | VARCHAR(11) | 否 | 是 |
sex | 性别 | VARCHAR(1) | 否 | 是 |
| 邮箱 | VARCHAR(32) | 否 | 是 |
pic | 用户头像保存路径 | VARCHAR(32) | 否 | 是 |
idcard | 身份证 | VARCHAR(18) | 否 | 是 |
表2-2 管理员表(admin)
列 名 | 字段名称 | 类型 | 主键 | 可否为空 |
id | 管理员ID | INT(32) | 是 | 否 |
username | 管理员名 | VARCHAR(32) | 否 | 否 |
password | 管理员密码 | VARCHAR(32) | 否 | 否 |
quanxian | 管理员权限等级 | VARCHAR(32) | 否 | 否 |
表2-3 商铺表(shops)
列 名 | 字段名称 | 类型 | 主键 | 可否为空 |
sid | 商铺ID | INT(32) | 是 | 否 |
sname | 商铺名 | VARCHAR(32) | 否 | 否 |
location | 商铺位置 | VARCHAR(32) | 否 | 否 |
area | 商铺面积 | VARCHAR(32) | 否 | 否 |
price | 商铺价格/月 | DECIMAL(10) | 否 | 否 |
information | 简介 | VARCHAR(255) | 否 | 是 |
details | 详细介绍 | VARCHAR(255) | 否 | 是 |
state | 商铺状态 | INT(1) | 否 | 否 |
pic | 商铺封面图 | VARCHAR(255) | 否 | 否 |
pics | 商铺图集 | VARCHAR(255) | 否 | 否 |
stime | 营业时间 | VARCHAR(32) | 否 | 是 |
score | 商铺评分 | DOUBLE(3) | 否 | 否 |
type | 商铺分类 | INT(1) | 否 | 否 |
userid | 当前租赁用户id | INT(32) | 否 | 是 |
表2-4 合同表(contract)
列 名 | 字段名称 | 类型 | 主键 | 可否为空 |
id | 合同ID | VARCHAR(32) | 是 | 否 |
userid | 用户ID | INT(32) | 否 | 否 |
shopid | 商铺ID | INT(32) | 否 | 否 |
name | 用户姓名 | VARCHAR(32) | 否 | 否 |
starttime | 开始时间 | VARCHAR(32) | 否 | 否 |
endtime | 到期时间 | VARCHAR(32) | 否 | 否 |
contract | 合同保存路径 | VARCHAR(255) | 否 | 否 |
state | 合同状态 | INT(1) | 否 | 否 |
price | 合同金额 | DECIMAL(10) | 否 | 否 |
表2-5 公告和活动表(announcement)
列 名 | 字段名称 | 类型 | 主键 | 可否为空 |
id | 公告ID | INT(32) | 是 | 否 |
title | 公告标题 | VARCHAR(255) | 否 | 否 |
content | 公告内容 | VARCHAR(9999) | 否 | 否 |
type | 公告类型 | INT(1) | 否 | 否 |
time | 开始时间 | DATETIME | 否 | 否 |
表2-6 轮播图信息表(banner)
列 名 | 字段名称 | 类型 | 主键 | 可否为空 |
id | ID | INT(32) | 是 | 否 |
img | 图片路径 | VARCHAR(255) | 否 | 否 |
target | 链接地址 | VARCHAR(255) | 否 | 是 |
表2-7 评论表(comment)
列 名 | 字段名称 | 类型 | 主键 | 可否为空 |
id | 评论ID | INT(32) | 是 | 否 |
shopid | 店铺ID | INT(32) | 否 | 否 |
name | 评论者昵称 | VARCHAR(32) | 否 | 否 |
content | 评论内容 | VARCHAR(255) | 否 | 否 |
score | 评分 | VARCHAR(10) | 否 | 否 |
time | 评论时间 | DATETIME | 否 | 否 |
表2-8 聊天记录表(chatgroup)
列 名 | 字段名称 | 类型 | 主键 | 可否为空 |
id | 记录ID | INT(32) | 是 | 否 |
nickname | 用户昵称 | VARCHAR(32) | 否 | 否 |
username | 用户名 | VARCHAR(32) | 否 | 否 |
content | 记录内容 | VARCHAR(255) | 否 | 否 |
pic | 头像地址 | VARCHAR(255) | 否 | 否 |
date | 记录时间 | DATETIME | 否 | 否 |
3.1 用户部分
功能描述:
用户注册是用户通过手机号或者邮箱,注册为系统用户,填写用户名,手机时,使用Ajax后台判断是否已经注册过,并给出提示,避免重复注册。使用手机号码注册时,填入手机号,点击获取验证码按钮,验证码就会发送到手机,填入验证码即可注册成功。使用邮箱注册时,填入自己的邮箱地址,点击获取验证码,验证码就会发送到你的邮箱中,填入邮箱中的验证码即可注册成功。
图3-1 用户注册界面图
图3-2 注册用户名已存在
Service层:
public ResultInfo register(User user) {
// 校验用户名
User u1 = userDao.findByUsername(user.getUsername());
if (u1 != null) {
return new ResultInfo(false, "用户名已存在");
}
// 校验手机号
User u2 = userDao.findByTelephone(user.getTelephone());
if (u2 != null) {
return new ResultInfo(false, "手机号已存在");
}
// 校验邮箱
User u3 = userDao.findByEmail(user.getEmail());
if (u3 != null) {
return new ResultInfo(false, "邮箱已存在...");
}
// 密码加密
String md5Password = Md5Utils.encodeByMd5(user.getPassword());
user.setPassword(md5Password);
// 保存用户
userDao.save(user);
return new ResultInfo(true);
}
Controller层
// 用户注册
@RequestMapping(value ="/register",produces = "application/json;charset=utf-8")
@ResponseBody
public ResultInfo register(User user, String smscode,String emailcode,Integer type) {
if(type==1) {
// 手机验证码
String sessionCode = (String) session.getAttribute("smsCode_" + user.getTelephone());
if (sessionCode == null || !sessionCode.equals(smscode)) {
return new ResultInfo(false, "手机验证码错误!");
}
}else if(type==2)
{ //邮箱验证码
String sessionCode = (String) session.getAttribute("emailCode_" + user.getEmail());
if (sessionCode == null || !sessionCode.equals(emailcode)) {
return new ResultInfo(false, "邮箱验证码错误!");
}
}else{
return new ResultInfo(false, "其他错误!");
}
// 调用service完成注册 昵称默认为用户ID
user.setNickname("用户"+user.getUid());
ResultInfo resultInfo = userService.register(user);
// 进行判断和页面跳转
if (resultInfo.getCode()==0) {
// 注册成功,清除session的验证码
session.removeAttribute("smsCode_"+user.getTelephone());
session.removeAttribute("emailCode_" + user.getEmail());
return new ResultInfo(true);
} else {
// 注册失败
return resultInfo;
}
}
功能描述:
用户使用注册的用户名和密码登录系统,或者使用注册时的手机号,通过短信验证码登录系统。登录页面使用Ajax方式后台异步发送请求,在收到结果后可以直接提示成功或失败,不需要再跳转到其他页面,使用手机登录时,输入注册时使用的手机号,然后点击获取验证码按钮,验证码短信就会发送到手机上,输入验证码即可登录到系统。忘记密码可以使用此功能登录。或者使用密码找回功能找回密码。
图3-3 用户账号登录界面图
图3-4 用户手机登录界面图
Service层
密码登录:
public ResultInfo pwdLogin(User user) {
User u1 = userDao.findByUsername(user.getUsername());
if (null==u1) {
return new ResultInfo(false, "此用户不存在");
}
String md5Pwd = Md5Utils.encodeByMd5(user.getPassword());
if (!md5Pwd.equals(u1.getPassword())) {
return new ResultInfo(false, "密码不正确");
}
return new ResultInfo(true, "登录成功", u1);
}
短信验证码登录:
public ResultInfo smsLogin(String telephone) {
User u1 = userDao.findByTelephone(telephone);
if (u1 == null) {
return new ResultInfo(false, "此手机号未注册");
}
return new ResultInfo(true, "登录成功", u1);
}
Controller层
密码登录:
/**
* 密码登录
* @param user 用户信息
* @param code 图形验证码
* @return ResultInfo
*/
@RequestMapping(value = "/pwdLogin",produces = "application/json;charset=utf-8")
@ResponseBody
public ResultInfo pwdLogin(User user,String code)
{
String sessionCode = session.getAttribute("imgCode").toString();
if(code!=null && !"".equals(code) && code.toUpperCase().equals(sessionCode)) {
ResultInfo resultInfo = userService.pwdLogin(user);
if (resultInfo.getCode()==0) {
session.setAttribute("User", resultInfo.getData());
session.removeAttribute("imgCode");
}
return resultInfo;
}else{
return new ResultInfo(false,"验证码错误!");
}
}
手机验证码登录:
/**
* 手机登录
* @param telephone 电话
* @param smscode 验证码
* @return ResultInfo
*/
@RequestMapping(value = "/smsLogin",produces = "application/json;charset=utf-8")
@ResponseBody
public ResultInfo smsLogin(String telephone,String smscode)
{
String code = (String) session.getAttribute("smsCode_" + telephone);
if(code!=null && !"".equals(code) && code.equals(smscode)) {
ResultInfo resultInfo = userService.smsLogin(telephone);
// 写入session
if (resultInfo.getCode()==0) {
session.setAttribute("User", resultInfo.getData());
session.removeAttribute("smsCode_"+telephone);
}
return resultInfo;
}else{
return new ResultInfo(false,"验证码错误!");
}
}
功能描述:
用户忘记密码后通过此功能可找回密码,可以通过注册时使用的手机找回,也可以使用注册时使用的邮箱找回,在使用手机找回时,输入注册时使用的手机号,点击获取验证码按钮,手机将会收到一条验证码信息,输入验证码后进入下一步,通过邮箱找回时,点击获取验证码按钮,邮箱将会收到验证码,输入验证码信息也可进入下一步。
图3-5 找回密码界面图
输入验证码后,就来到了此界面,自动查询出手机或邮箱绑定的用户名,防止用户因忘记用户名而无法登录,然后在下方输入新的密码,确认新的密码,点击重置密码按钮即可重置完成密码操作。
图3-6 确认找回密码界面图
Service层
public User forget(User user) {
if(user.getTelephone()!=null)
{
return userDao.findByTelephone(user.getTelephone());
}else if(user.getEmail()!=null)
{
return userDao.findByEmail(user.getEmail());
}
return null;
}
Controller层
@RequestMapping("/forget")
public String forget(User user, String smscode,String emailcode,Integer type) {
Integer smsId = 1;
Integer emailId = 2;
if(user==null||type==null)
{
request.setAttribute("errormsg","非法访问!");
return "/error.jsp";
}
if(type.equals(smsId)) {
// 手机验证码
String sessionCode = (String) session.getAttribute("smsCode_" + user.getTelephone());
if (sessionCode == null || !sessionCode.equals(smscode)) {
request.setAttribute("errormsg","手机验证码错误!");
return "/error.jsp";
}
}else if(type.equals(emailId))
{ //邮箱验证码
String sessionCode = (String) session.getAttribute("emailCode_" + user.getEmail());
if (sessionCode == null || !sessionCode.equals(emailcode)) {
request.setAttribute("errormsg","邮箱验证码错误!");
return "/error.jsp";
}
}else{
request.setAttribute("errormsg","其他错误!");
return "/error.jsp";
}
session.setAttribute("fUser",userService.forget(user));
return "/forgetChange.jsp";
}
/**
* 找回密码第二步,改密码
* @param password 新密码
* @return ResultInfo
*/
@RequestMapping(value = "/forgetChange", produces = "application/json;charset=utf-8")
@ResponseBody
public ResultInfo forgetChange(String password,String rePassword) {
if(!password.equals(rePassword)) {
return new ResultInfo(false,"两次密码不一致!");
}
User user = (User) session.getAttribute("fUser");
if(user==null)
{
return new ResultInfo(false,"非法请求!");
}
user.setPassword(Md5Utils.encodeByMd5(password));
userService.updateInfo(user);
session.removeAttribute("fUser");
return new ResultInfo(true);
}
3.2 个人中心
功能描述:
在此页面,可以查看和修改用户的基本信息。包括姓名,手机号,邮箱,性别,身份证等信息,采用Ajax的方式提交到服务器,可以在页面上通过信息框提示是否更新成功,不必跳转页面。
图3-7 个人信息界面图
Controller层
@RequestMapping(value = "/updateInfo", produces = "application/json;charset=utf-8")
@ResponseBody
public ResultInfo updateInfo(User user){
userService.updateInfo(user);
//重新写入session
User u1 = userService.findByUid(user.getUid());
session.setAttribute("User", u1);
return new ResultInfo(true);
}
功能描述:
更换头像功能,可以更换用户的头像图片,首先点击上传头像按钮,弹出选择文件对话框,选中要上传的头像图片,点击确定,方框中就会显示选择的图片,此时,头像图片并未上传至服务器,而是读取了文件的内容,将其显示在浏览器中,当点击提交头像时,图片才被送到服务器中保存。
图3-8 更换头像界面图
Controller层
@RequestMapping(value = "/updatePic", produces = "application/json;charset=utf-8")
@ResponseBody
public ResultInfo updatePic(MultipartFile file) throws IOException {
if (!file.isEmpty()) {
User user = (User) session.getAttribute("User");
if (user != null) {
String picPath = "/pic/" + user.getUid() + ".jpg";
File myFile = new File(request.getServletContext().getRealPath("/") + picPath);
file.transferTo(myFile);
user.setPic(picPath);
userService.updateInfo(user);
//重新写入session
User u1 = userService.findByUid(user.getUid());
session.setAttribute("User", u1);
return new ResultInfo(true);
}
return new ResultInfo(false);
}
return new ResultInfo(false);
}
前端
layui.use('upload', function(){
var $ = layui.jquery
,upload = layui.upload;
//头像上传
var uploadInst = upload.render({
elem: '#test1'
,url: '${pageContext.request.contextPath}/user/updatePic' //上传接口
,bindAction:'#test2'//执行文件上传动作
,auto: false
,choose:function (obj) {
obj.preview(function(index, file, result){
$('#demo1').attr('src', result); //图片转为base64赋给src属性
});
}
,done: function(res){
//如果上传失败
if(res.code > 0){
return layer.msg('上传失败');
}
//上传成功
$('#touxiang').attr('src', '${pageContext.request.contextPath}/${User.pic}?'+new Date().getTime());
$('#htouxiang').attr('src', '${pageContext.request.contextPath}/${User.pic}?'+new Date().getTime());
return layer.msg('上传成功');
}
,error: function(){
//失败重传
var demoText = $('#demoText');
demoText.html('<span style="color: #FF5722;">上传失败</span> <a class="layui-btn layui-btn-xs demo-reload">重试</a>');
demoText.find('.demo-reload').on('click', function(){
uploadInst.upload();
});
}
});
})
功能描述:
用户的修改密码功能,输入自己的旧密码,然后输入新密码,确认新密码,点击确认修改按钮,即可成功修改密码。修改密码后,登录信息将失效,需要重新登录,自动打开登录窗口,并提示用户重新登录。
图3-9 修改密码界面图
Controller层
@RequestMapping(value = "/updatePass", produces = "application/json;charset=utf-8")
@ResponseBody
public ResultInfo updatePass(String oldPassword,String newPassword,String againPassword) {
ResultInfo result;
if (!newPassword.equals(againPassword)) {
result = new ResultInfo(false, "两次密码输入不一致!");
} else {
User user = (User) session.getAttribute("User");
if (user != null) {
if (user.getPassword().equals(Md5Utils.encodeByMd5(oldPassword))) {
if (oldPassword.equals(newPassword)) {
result = new ResultInfo(false, "旧密码不能与新密码一致!");
} else {
user.setPassword(Md5Utils.encodeByMd5(newPassword));
userService.updateInfo(user);
session.removeAttribute("User");
result = new ResultInfo(true);
}
} else {
result = new ResultInfo(false, "旧密码输入错误!");
}
} else {
result = new ResultInfo(false, "非法请求!");
}
}
return result;
}
功能描述:
用户合同管理功能,在此页面中,可以看到当前用户所签订的所有合同信息,包括(合同编号,合同商铺,开始时间,终止时间,成交价格,合同状态),并且可以下载合同源文件。合同状态分别为 未付款,审核中,已生效,已过期 四个状态。由系统内的定时任务自动管理,每十分钟刷新一次状态。在已生效和已过期状态时,可以下载合同,未支付状态时,可以跳转到付款页面。
图3-10 我的合同界面图
Controller层
@RequestMapping("/userHetong")
public String userHetong(){
User user = (User) session.getAttribute("User");
if (user == null){
return "redirect:/index.jsp";
}else{
List<Contract> contracts = contractService.findByUserid(user.getUid());
request.setAttribute("contracts",contracts);
return "/home_hetong.jsp";
}
}
功能描述:
用户商铺管理功能,在此页面中,可以看到用户正在经营的商铺信息,包括商铺编号,商铺名称,商铺位置,商铺面积 ,商铺类型,营业时间 ,商铺评分等信息。评分由顾客评价后给出的评分根据一定算法进行计算,用户只能看到分数。同时,用户可以编辑商铺的信息,包括名称,类型,营业时间,简介,详细介绍,封面图,图集等内容。在租赁商铺后可以更新为自己所经营的信息。
图3-11 我的商铺界面图
Controller层
@RequestMapping("/userShop")
public String userShop(){
User user = (User) session.getAttribute("User");
if (user == null){
return "redirect:/index.jsp";
}else{
List<Shop> shops = shopService.findShopsByUid(user.getUid());
request.setAttribute("shops",shops);
return "/home_shop.jsp";
}
}
@RequestMapping("/editShop/{sid}")
public String editShop(@PathVariable("sid") Integer sid)
{
User user = (User) session.getAttribute("User");
if(user==null)
{
return "/Login.jsp";
}
Shop shop = shopService.findShopsBySid(sid);
if(shop.getUserid()==user.getUid())
{
request.setAttribute("Shop",shop);
return "/home_shop_edit.jsp";
}
return null;
}
@RequestMapping(value = "/setShop", produces = "application/json;charset=utf-8")
@ResponseBody
public ResultInfo setShop(Shop shop){
User user = (User) session.getAttribute("User");
if(user==null)
{
return new ResultInfo(false);
}
return shopService.update(shop);
}
3.3 商铺租赁
功能描述:
展示当前正在出租的商铺信息,包括位置,价格,面积,详细介绍等。可选择租赁期限,点击立即租赁,即可进入商铺租赁流程。若当前未登录账号,则弹出账号登录模态框,登录账号后依然停留在本页面,可以继续操作。
图3-12 商铺列表页图
图3-13 商铺详情页图
ShopsController层
@RequestMapping("/rent")
public String rent(@RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "12") Integer pageSize) {
PageBean pageBean = shopsService.listRent(pageNum, pageSize);
request.setAttribute("pb", pageBean);
return "/rent_list.jsp";
}
ShopSevice层
public PageBean listRent(Integer pageNum, Integer pageSize)
{
Integer total = shopsDao.findRentCount();
Integer index = (pageNum - 1) * pageSize;
List<Shop> list = shopsDao.findRentByPage(index, pageSize);
return new PageBean(total, list, pageNum, pageSize);
}
前端
<div class="layui-row layui-col-space10">
<s:if test="${empty pb.data}">
<div style="text-align: center;padding-top: 50px">暂无商家</div>
</s:if>
<s:forEach items="${pb.data}" var="shop">
<div class="layui-col-md3 layui-col-sm4">
<div class="item">
<a href="${pageContext.request.contextPath}/detail/${shop.sid}">
<img width="100%" height="200px" src="${pageContext.request.contextPath}${shop.pic}">
</a>
<a href="${pageContext.request.contextPath}/detail/${shop.sid}">
<div style="height:47px;padding: 2px">
<p class="text">${shop.sname}</p>
<div class="layui-row">
<div class="layui-col-xs8 layui-col-md9 layui-col-lg10">
<b>${shop.stime}</b>
</div>
<div class="layui-col-xs4 layui-col-md3 layui-col-lg2" style="text-align: right;color: red">
<b>${shop.score}</b>
</div>
</div>
</div>
</a>
</div>
</div>
</s:forEach>
</div>
功能描述:
租赁商铺时,选择好商铺和租赁时间,点击立即租赁后,即可进入在线签署租赁合同页面,生成合同内容,用户在详细阅读合同后,在下方签字处手写签字,然后点击提交,即可完成签署合同。用户提交后,后台使用itext组件,生成PDF文档,并将签名插入到指定位置,保存在/pdf目录下。同时修改商铺状态为已被租赁,然后跳转到支付页面,用户进行支付操作。若用户未完成支付操作,没有商铺操作权限,若用户10分钟后仍未完成支付,商铺将被系统回收,重置为租赁状态。
图3-14 签署合同页图
Controller层
@RequestMapping("/create")
public String nameimg(Integer sid,String starttime,String endtime, String namepic,Double price) {
User user = (User) session.getAttribute("User");
if(user!=null && !"".equals(namepic))
{
Shop shop = shopsService.findShopsBySid(sid);
String picPath = "upload\\" + Md5Utils.encodeByMd5(namepic) + ".png";
String realPicPath = request.getServletContext().getRealPath("/") + picPath;
Base64Util.base64ToImage(namepic,realPicPath);
String path = "pdf/"+UUID.randomUUID()+".pdf";
String savepath = request.getServletContext().getRealPath("/") +path;
Contract contract = new Contract();
contract.setId(UuidUtils.getUuid());
contract.setName(user.getNickname());
contract.setUserid(user.getUid());
contract.setShopid(shop.getSid());
contract.setStarttime(starttime);
contract.setEndtime(endtime);
contract.setContract(path);
contract.setState(0);
contract.setPrice(price);
/*添加合同记录 并 修改店铺状态和userid*/
contractService.add(contract,shop,user);
try{
PdfUtil.createPDF(user.getNickname(),shop.getLocation(),shop.getArea(),starttime,endtime,price.toString(),NumToCnUtil.toChinese(price.toString()),realPicPath,savepath);
}catch (Exception e)
{
request.setAttribute("errormsg","合同生成失败!");
return "/error.jsp";
}
return "redirect:/contract/pay/"+contract.getId();
}
return "/error.jsp";
}
Service层
public void add(Contract contract, Shop shop, User user)
{
contractDao.save(contract);
shop.setState(2);
shop.setUserid(user.getUid());
shopsDao.update(shop);
}
前端
$(function () {
$("#box").jSignature();//初始化画板
});
layui.use('form', function(){
var form = layui.form;
//监听表单提交
form.on('submit(tijiao)', function (data) {
var datapair = $("#box").jSignature("getData","image");
$("#namepic").val(datapair[1]);
return true;
});
});
function chongxie(){
$("#box").jSignature("reset");
$("#image").attr('src','');
}
功能描述:
本功能调用微信支付完成,签完合同后,弹出支付页面,用户使用手机扫码在线支付。在支付时,后台使用定时器每秒钟查询一次支付状态,若支付成功,跳转到成功页面,在线支付完成。若超时10分钟未支付,页面提示超时,订单将被删除,商铺被回收,重新置为租赁状态。若支付成功,商铺进入审核状态,后台管理员审核通过后,即可正常使用。
图3-15 在线支付页图
Controller层
@RequestMapping("/pay/{id}")
public String pay(@PathVariable("id") String id)
{
Contract contract = contractService.findByid(id);
if(contract!=null)
{
String payurl = PayUtils.createOrder(id,contract.getPrice()*100); //支付金额 单位:分
request.setAttribute("payUrl",payurl);
request.setAttribute("contract",contract);
return "/pay.jsp";
}else{
request.setAttribute("errormsg","订单不存在!");
return "/error.jsp";
}
}
/**
* 支付成功回调
* @return
* @throws Exception
*/
@RequestMapping(value = "/payNotify", produces = MediaType.APPLICATION_XML_VALUE)
@ResponseBody
public Map<String, String> payNotify() throws Exception
{
ServletInputStream inputStream = request.getInputStream();
XmlMapper xmlMapper = new XmlMapper();
Map param = xmlMapper.readValue(inputStream, Map.class);
contractService.updateState(param);
Map<String, String> result = new HashMap<>(10);
result.put("return_code", "SUCCESS");
result.put("return_msg", "OK");
return result;
}
/**
* 查看支付状态
*/
@RequestMapping(value = "/checkPay",produces = "application/json;charset=utf-8")
@ResponseBody
public ResultInfo checkPay(String id) {
Contract contract = contractService.findByid(id);
if (contract.getState() == 1) {
return new ResultInfo(true);
} else {
return new ResultInfo(false);
}
}
Service层
public void updateState(Map param) {
//支付成功通知的订单号
String orderId = (String) param.get("out_trade_no");
Contract contract = contractDao.findById(orderId);
contract.setState(3);
contractDao.update(contract);
}
前端
生成二维码
var qr = window.qr = new QRious({
element: document.getElementById('qrious'),
size: 300,
value: '${payUrl}'
});
10分钟倒计时
var num=60*10;
setInterval(function () {
// 发送ajax请求,查询是否支付成功
var url = '${pageContext.request.contextPath}/contract/checkPay?id=${contract.id}';
$.get(url, function (resp) {
if (resp.code==0) {
location.href = '${pageContext.request.contextPath}/success.jsp';
}
});
num--;
$('#jishiqi').html(parseINT(num/60)+'分'+num%60+'秒后失效,请及时付款!')
}, 1000);
超时跳转到错误页
setTimeout(function () {
location.href = '${pageContext.request.contextPath}/error.jsp';
}, 600000);
3.4 商家展示
功能描述:
列表页展示入驻的全部商家,分页显示,按照评分从高到低排名,也可以按商家分类展示。详情页展示已经在本商城入驻的商家信息,包括简介,商家位置,分类,营业时间,商家评分,详细介绍等。
图3-16 商家列表页图
图3-17 商家详情页图
Controller层
商家列表
@RequestMapping("/shop")
public String shop(@RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "12") Integer pageSize,@RequestParam(defaultValue = "-1") Integer type) {
PageBean pageBean;
if(type==-1)
{
pageBean = shopsService.listShop(pageNum, pageSize);
}else{
pageBean = shopsService.listShop(pageNum, pageSize,type);
}
request.setAttribute("pb", pageBean);
return "/shop_list.jsp";
}
商家详情
@RequestMapping("/detail/{sid}")
public String detail(@PathVariable("sid") Integer sid) {
Shop shop = shopsService.findShopsBySid(sid);
request.setAttribute("shop", shop);
if(shop.getState()==1)
{
List<Comment> comments = commentService.findByShopid(sid);
request.setAttribute("Comments", comments);
return "/shop_detail.jsp";
}else{
return "/rent_detail.jsp";
}
}
Service层
public PageBean listShop(Integer pageNum, Integer pageSize)
{
Integer total = shopsDao.findShopCount();
Integer index = (pageNum - 1) * pageSize;
List<Shop> list = shopsDao.findShopByPage(index, pageSize);
return new PageBean(total, list, pageNum, pageSize);
}
功能描述:
顾客可以对已经入驻的商家进行留言评论和评分,商家评分根据顾客的评分按照评分公式自动计算,商家无法修改。若评分小于等于1星,商家将被警告。
图3-18 商家详情页评论图
Controller层
@RequestMapping(value="/shop/comment",produces = "application/json")
@ResponseBody
public ResultInfo comment(Comment comment)
{
commentService.save(comment);
return new ResultInfo(true);
}
Service层
public void save(Comment comment) {
comment.setTime(new Date());
commentDao.save(comment);
}
前端
<form class="layui-form layui-form-pane" action="">
<input name="shopid" type="hidden" value="${shop.sid}">
<input id="score" name="score" type="hidden" value="5">
<input id="content" name="content" type="hidden">
<div class="">
<div id="myEditor" style="display: none;"></div>
<div class="layui-inline">
<label class="layui-form-label">姓名:</label>
<div class="layui-input-inline">
<input style="width: 150px" name="name" lay-verify="required" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-inline">
<label style="margin-top: 3px" class="layui-form-label">评分:</label>
<div id="myPingFen"></div>
</div>
<div style="width: 200px;float:right" id="send" class="layui-btn" lay-submit lay-filter="pinglun-tijiao" >提交</div>
</div>
</form>
<br><br>
<div class="layui-card-header">
评论列表
</div>
<s:if test="${empty Comments}">
<div id="noping" class="layui-card">
<div class="layui-card-body">暂无评论,快来评论吧!</div>
</div>
</s:if>
<div id="pinglist">
<s:forEach items="${Comments}" var="Comment">
<div class="layui-card">
<div class="layui-card-header"><b>${Comment.name}</b><img src="${pageContext.request.contextPath}/imgs/star/${Comment.score}.png" alt=""> ( ${Comment.score}分 ) <i style="color: #BBBBBB">${Comment.time}</i></div>
<div class="layui-card-body">${Comment.content}</div>
</div>
</s:forEach>
</div>
3.5 聊天大厅
聊天大厅使用WebSocket技术,实现聊天信息的实时传输。
图3-19 聊天大厅图
功能描述:
此处配置WebSocket的监听路径,聊天大厅连接此处监听的路径,可以向此路径发送数据,服务器监听向此路径发送的数据,收到数据后进行处理。
WebSocketConfig 配置
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(webSocketMessageHandler(),"/websocket").addInterceptors(new MyWebSocketHandshakeInterceptor());
registry.addHandler(webSocketMessageHandler(), "/sockjs/websocket").addInterceptors(new MyWebSocketHandshakeInterceptor()).withSockJS();
}
功能描述:
在websocket连接前,对websocket的session进行处理,添加httpsession里的有关用户登录认证的字段:User,以便在聊天大厅获取用户名等信息,将websocket的用户和网站用户联系起来。
WebSocketHandshakeInterceptor 握手拦截器
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
Map<String, Object> attributes) throws Exception {
//Before Handshake--websocket 握手之前的方法
if (request instanceof ServletServerHttpRequest) {
ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
HttpSession httpSession = servletRequest.getServletRequest().getSession(false);
if (httpSession != null) {
attributes.put("httpsession",httpSession);
User user = (User) httpSession.getAttribute("User");
if (user != null) {
attributes.put("userid", String.valueOf(user.getUid()));
}else{
attributes.put("userid", "0");
}
}
}
return super.beforeHandshake(request, response, wsHandler, attributes);
}
功能描述:
处理收到的聊天消息,定义了自己的发送规则,服务器向客户端发送消息时,使用mtype标志,mtype为1时,表示用户发送聊天消息。mtype为2时,表示系统发送提示信息。mtype为3时,表示用户上线。mtype为4时,表示用户下线。mtype为5时,表示发送在线用户列表。客户端向服务器发送消息时,使用ttype标志,ttype值为“消息”时,表示发送的是聊天消息。
WebSocketMessageHandler 消息处理器
@Component
public class WebSocketMessageHandler extends TextWebSocketHandler {
@Autowired
private ChatGroupService chatGroupService;
private static final Map<String, ChatUser> USERS = new ConcurrentHashMap<>();
/**
* 建立连接后的处理
*/
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
HttpSession httpsession = (HttpSession) session.getAttributes().get("httpsession");
if(httpsession!=null)
{
User user = (User) httpsession.getAttribute("User");
if(user!=null) {
ChatUser chatuser = new ChatUser();
chatuser.setUser(user);
chatuser.setSession(session);
USERS.put(String.valueOf(chatuser.getUser().getUid()), chatuser);
putChatList(String.valueOf(chatuser.getUser().getUid()));
putOnline(chatuser);
}
}
super.afterConnectionEstablished(session);
}
/**
* 向所有用户发送用户下线
* @param cu ChatUser
*/
private void putOffline(ChatUser cu)
{
HashMap<String,Object> map = new HashMap<>(10);
map.put("mtype",4);
map.put("username",cu.getUser().getUsername());
sendMessageToAllUsers(JSONUtils.toJSONString(map));
}
/**
* 向所有用户发送用户上线
* @param cu ChatUser
*/
private void putOnline(ChatUser cu)
{
HashMap<String,Object> map = new HashMap<>(10);
map.put("mtype",3);
map.put("username",cu.getUser().getUsername());
map.put("nickname",cu.getUser().getNickname());
map.put("pic",cu.getUser().getPic());
sendMessageToAllUsers(JSONUtils.toJSONString(map),String.valueOf(cu.getUser().getUid()));
}
/**
* 向指定用户发送在线用户列表
* @param uid 用户ID
*/
private void putChatList(String uid){
Map<String,Object> map = new HashMap<>(10);
Map<String,Object> map2 = new HashMap<>(10);
map.put("mtype",5);
map.put("count",USERS.size());
Integer i=0;
for(Map.Entry<String, ChatUser> entry : USERS.entrySet()){
Map<String,Object> map3 = new HashMap<>(10);
map3.put("nickname",entry.getValue().getUser().getNickname());
map3.put("username",entry.getValue().getUser().getUsername());
map3.put("pic",entry.getValue().getUser().getPic());
map2.put(String.valueOf(i),map3);
i++;
}
map.put("data",map2);
String json = JSONUtils.toJSONString(map);
try {
USERS.get(uid).getSession().sendMessage(convertWebSocketTextMessage(json));
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 连接关闭后的处理
* @param session 被关闭的session
* @param status 状态
* @throws Exception 发送失败异常
*/
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
String userid = (String) session.getAttributes().get("userid");
//通知全部用户有人下线
if(userid!=null)
{
putOffline(USERS.get(userid));
USERS.remove(userid);
}
super.afterConnectionClosed(session, status);
}
/**
* 文本消息处理
* @param session 发送者session
* @param message 发送内容
*/
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message){
String typeMsg = "消息";
String userid = (String) session.getAttributes().get("userid");
String payload = message.getPayload();
@SuppressWarnings("unchecked")
HashMap<String, Object> map = (HashMap<String, Object>) JSONUtils.parse(payload);
String ttype = (String) map.get("ttype");
if(typeMsg.equals(ttype))
{
Date d = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
map.put("date",sdf.format(d));
map.put("mtype",1);
putMessages(userid,map);
}
}
/**
* 向指定用户发送消息
* @param userid 用户id
* @param map 消息
*/
private void putMessages(String userid, Map<String,Object> map)
{
//保存聊天记录到数据库
ChatHistory chatHistory = new ChatHistory();
chatHistory.setNickname((String) map.get("nickname"));
chatHistory.setUsername((String) map.get("username"));
chatHistory.setPic((String) map.get("pic"));
chatHistory.setContent((String) map.get("content"));
chatHistory.setDate((String) map.get("date"));
chatGroupService.save(chatHistory);
for(Map.Entry<String, ChatUser> entry : USERS.entrySet()){
WebSocketSession session = entry.getValue().getSession();
if (session.isOpen()) {
try {
map.put("isSelf",entry.getKey().equals(userid));
session.sendMessage(convertWebSocketTextMessage(JSONUtils.toJSONString(map)));
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private TextMessage convertWebSocketTextMessage(String textMessage) {
return new TextMessage(textMessage);
}
/**
* 发送消息给所有用户
* @param textMessage 消息
*/
private void sendMessageToAllUsers(String textMessage) {
for(Map.Entry<String, ChatUser> entry : USERS.entrySet()){
WebSocketSession session = entry.getValue().getSession();
if (session.isOpen()) {
try {
session.sendMessage(convertWebSocketTextMessage(textMessage));
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
功能描述:
聊天大厅页面连接服务器,然后可以发送消息给服务器。使用两种连接方式,一种是HTML5的Websocket连接,若用户的浏览器不知吹HTML5,则使用SockJS来模拟Websocket连接,这样就可以兼容多种浏览器。因为Websocket无活动60秒后会自动断开连接,所以前端使用javascript设置一个定时器,每55秒发送一次心跳数据,用来保持Websocket连接,服务器无需回复这个信息。
var wsPath='ws://${pageContext.request.getServerName()}:${pageContext.request.getServerPort()}${pageContext.request.contextPath}/';
var socket;
try {
if ('WebSocket' in window) {
socket = new WebSocket(wsPath+"websocket");
} else {
socket = new SockJS(wsPath+"sockjs/websocket");
}
} catch (e) {
console.log('创建连接失败!'+e);
}
socket.onmessage=function(ev){
var obj = eval('('+ev.data+')');
addMessage(obj)
};
setInterval(function () {
var str = JSON.stringify({
ttype:'心跳'
});
socket.send(str);
},55000);
功能描述:
用户在输入框输入内容后,点击发送按钮,聊天大厅使用JS判断用户是否登录,消息内容是否为空,通过后把消息内容拼接成json格式,然后通过Websocket发送给服务器。消息可以支持文字,表情,超链接,图片等格式。
$("#send").click(function(){
var u = '${User}';
if(u=='') {
layer.msg("您不能发送消息,请登录!");
return false;
}
var txt = layedit.getContent(index);
if(txt==''||txt==' ') {
layer.msg("消息不能为空!");
return false;
}
var str = JSON.stringify({
nickname:nickname,
username:username,
pic:pic,
content:txt,
ttype:'消息'
});
if(isEmpty(txt))
{
layer.msg("没有输入内容!");
}else {
socket.send(str);
layedit.setContent(index,'');
}
});
功能描述:
客户端收到消息后,判断消息类型,mtype为1时代表是聊天消息。将接收到的聊天消息格式化后,填充到消息模板里,然后追加到容器中,显示在页面上,同时判断这条消息是不是自己发的,如果是,则不播放消息提示音,如果不是则播放收到消息的提示音,提醒用户收到消息。
var audio= new Audio("${pageContext.request.contextPath}/audio/default.mp3");
if(msg.mtype==1)
{
var box = $("#msgtmp").clone();
box.show();
box.appendTo("#chatContent");
box.find('[ff="nickname"]').html(msg.isSelf? ('<i>'+msg.date+'</i>'+msg.nickname+' ('+msg.username+')'):(msg.nickname+' ('+msg.username+')'+'<i>'+msg.date+'</i>'));
box.find('[ff="pic"]').html('<img src="'+msg.pic+'">');
box.find('[ff="msgdate"]').html(msg.date);
box.find('[ff="content"]').html(msg.content);
box.addClass(msg.isSelf? 'layim-chat-mine':'');
scroll_To(1000);
//不是自己发的消息播放叮咚声
if(!msg.isSelf)
{
audio.play();
}
}
3.6 定时任务
定时任务主要处理的是长时间未支付的合同和计算过期合同,使用Spring框架的@Scheduled注解实现。定时任务是每隔一段时间自动运行一次的任务,启动后无需人工管理,即可自动执行。
功能描述:
定时处理超过支付时间但是仍未支付的合同订单。每10分钟扫描一次,若有超时未支付的合同订单,将相关联的商铺重新置为租赁状态,然后删除这个合同,将相关联的用户置为空。
private void timeOutTiming(){
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
List<Contract> cons = contractService.findByNoPay();
for (Contract con : cons) {
String starttime = con.getStarttime();
try{
long startDate = format.parse(starttime).getTime();
long nowDate = System.currentTimeMillis();
long minute = (nowDate-startDate)/1000/60;
if(minute>=10)
{
//十分钟未支付,删除合同并恢复租赁
contractService.delete(con.getId());
}
}catch (Exception e){
e.printStackTrace();
}
}
}
Service层
public ResultInfo delete(String id) {
//改为租赁状态
shopsDao.updateState(contractDao.findById(id).getShopid(),0);
shopsDao.updateUid(contractDao.findById(id).getShopid(),0);
contractDao.delete(id);
return new ResultInfo(true);
}
功能描述:
定时处理到期合同,合同到期后,将商铺重置为过期审核状态,供后台管理员审核和修改,然后将合同置为过期状态,将相关联的用户置为空。
private void expiredTiming(){
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
List<Contract> cons = contractService.findByValid();
for (Contract con : cons) {
String endtime = con.getEndtime();
try{
long endDate = format.parse(endtime).getTime();
long nowDate = System.currentTimeMillis();
long expired = nowDate-endDate;
if(expired>0){
//合同过期 恢复租赁状态
contractService.updateStateById(con.getId(),2);
}
}catch (Exception e){
e.printStackTrace();
}
}
}
Service层
public void updateStateById(String id,Integer state) {
Contract contract = contractDao.findById(id);
contract.setId(id);
contract.setState(state);
contractDao.update(contract);
//店铺租赁过期状态
shopsDao.updateState(contract.getShopid(),2);
shopsDao.updateUid(contract.getShopid(),0);
}
3.7 后台管理
功能描述:
对用户提交的合同进行审核,在后台可以查看合同全部内容,验证合同内容和签字是否规范。对签字不规范的合同可以进行驳回处理,对签字规范的合同进行通过处理。
图3-20 最新合同审核页图
图3-21 合同查看
Controller层
private boolean check(){
Admin admin = (Admin) session.getAttribute("Admin");
return admin != null;
}
@RequestMapping(value = "/data/getCheckCont",produces = "application/json;charset=utf-8")
@ResponseBody
public PageBean getCheckCont(@RequestParam(defaultValue = "1")Integer page, @RequestParam(defaultValue = "10")Integer limit)
{
if(check()) {
return contractService.findCheckByPage(page, limit);
}
return null;
}
@RequestMapping(value = "/data/setYesCont",produces = "application/json;charset=utf-8")
@ResponseBody
public ResultInfo setYesCont(Contract contract)
{
if(check()) {
Contract contract1 = contractService.findByid(contract.getId());
Shop shop = shopsService.findShopsBySid(contract1.getShopid());
shop.setState(1);
shopsService.addOrUpdate(shop);
contract.setState(1);
return contractService.addOrUpdate(contract);
}
return new ResultInfo(false,"没有操作权限!");
}
@RequestMapping(value = "/data/setNoCont",produces = "application/json;charset=utf-8")
@ResponseBody
public ResultInfo setNoCont(Contract contract)
{
if(check()) {
contract.setState(4);
return contractService.addOrUpdate(contract);
}
return new ResultInfo(false,"没有操作权限!");
}
Service层
public ResultInfo addOrUpdate(Contract contract) {
Contract c = contractDao.findById(contract.getId());
try {
if (c == null) {
contractDao.save(contract);
} else {
contractDao.update(contract);
}
return new ResultInfo(true);
}catch (Exception e){
return new ResultInfo(false);
}
}
public PageBean findCheckByPage(Integer pageNum, Integer pageSize) {
Integer count = contractDao.findCheckCount();
Integer index = (pageNum - 1) * pageSize;
List<Contract> data = contractDao.findCheckByPage(index, pageSize);
return new PageBean(count, data, pageNum, pageSize);
}
功能描述:
管理员对过期的商铺进行审核,重新设置商铺的相关信息,然后将商铺状态改为租赁状态,在网站上进行出租。先点击修改按钮,在弹出的窗口中重新设置店铺信息,然后点击出租按钮,即可进行出租。
图3-22 过期商铺审核页图
图3-23 过期商铺审核编辑页图
Controller层
@RequestMapping(value = "/data/getCheckRent",produces = "application/json;charset=utf-8")
@ResponseBody
public PageBean getCheckRent(@RequestParam(defaultValue = "1")Integer page, @RequestParam(defaultValue = "10")Integer limit)
{
if(check()) {
return shopsService.listCheckRent(page, limit);
}
return null;
}
@RequestMapping(value = "/data/setToRent",produces = "application/json;charset=utf-8")
@ResponseBody
public ResultInfo setToRent(Shop shop)
{
if(check()) {
return shopsService.updateState(shop.getSid(),0);
}
return new ResultInfo(false,"没有操作权限!");
}
Service层
public PageBean listCheckRent(Integer pageNum, Integer pageSize)
{
Integer total = shopsDao.findCheckRentCount();
Integer index = (pageNum - 1) * pageSize;
List<Shop> list = shopsDao.findCheckRentByPage(index, pageSize);
return new PageBean(total, list, pageNum, pageSize);
}
功能描述:
管理员对商城注册用户的管理,可以添加用户,修改用户信息,删除用户等。
图3-24 用户管理页图
图3-25 用户管理修改页图
Controller层
@RequestMapping(value = "/data/getUser",produces = "application/json;charset=utf-8")
@ResponseBody
public PageBean getUser(@RequestParam(defaultValue = "1")Integer page, @RequestParam(defaultValue = "10")Integer limit)
{
if(check()) {
return userService.findByPage(page, limit);
}
return null;
}
@RequestMapping(value = "/data/setUser",produces = "application/json;charset=utf-8")
@ResponseBody
public ResultInfo setUser(User user)
{
if(check()) {
return userService.addOrUpdateUser(user);
}
return new ResultInfo(false,"没有操作权限!");
}
@RequestMapping(value = "/data/delUser",produces = "application/json;charset=utf-8")
@ResponseBody
public ResultInfo delUser(Integer id)
{
if(check()) {
return userService.delUser(id);
}
return new ResultInfo(false,"没有操作权限!");
}
Service层
public PageBean findByPage(Integer pageNum, Integer pageSize) {
Integer count = userDao.findCount();
Integer index = (pageNum - 1) * pageSize;
List<User> data = userDao.findByPage(index, pageSize);
return new PageBean(count, data, pageNum, pageSize);
}
public ResultInfo addOrUpdateUser(User user) {
User u = userDao.findByUid(user.getUid());
try {
if (u == null) {
user.setPassword(Md5Utils.encodeByMd5("123456"));
userDao.save(user);
} else {
userDao.update(user);
}
return new ResultInfo(true);
}catch (Exception e){
return new ResultInfo(false);
}
}
public ResultInfo delUser(Integer id) {
userDao.delete(id);
return new ResultInfo(true);
}
功能描述:
超级管理员对管理员的管理,可以添加管理员,修改管理员信息,删除管理员等。
图3-26 管理员管理页图
图3-27 管理员管理修改页图
Controller层
@RequestMapping(value = "/data/getAdmin",produces = "application/json;charset=utf-8")
@ResponseBody
public PageBean getAdmin(@RequestParam(defaultValue = "1")Integer page, @RequestParam(defaultValue = "10")Integer limit)
{
if(check()) {
return adminService.findByPage(page, limit);
}
return null;
}
@RequestMapping(value = "/data/setAdmin",produces = "application/json;charset=utf-8")
@ResponseBody
public ResultInfo setAdmin(Admin admin)
{
if(isSuper()) {
return adminService.addOrUpdate(admin);
}
return new ResultInfo(false,"没有操作权限!");
}
@RequestMapping(value = "/data/delAdmin",produces = "application/json;charset=utf-8")
@ResponseBody
public ResultInfo delAdmin(Integer id)
{
if(isSuper()) {
return adminService.delete(id);
}
return new ResultInfo(false,"没有操作权限!");
}
前端
layui.use('table', function(){
var table = layui.table;
table.on('tool(demo)', function(obj){
var data = obj.data;
if(obj.event === 'del'){
layer.confirm('真的要删除吗?', function(index){
$.post('${pageContext.request.contextPath}/data/delAdmin?id='+data.id, {}, function(str){
if(str.code === 0)
{
obj.del();
layer.alert("删除成功");
}else(str.code === 1);
{
layer.alert(str.msg);
}
});
layer.close(index);
});
} else if(obj.event === 'edit'){
layer.open({
type: 2,
title:'修改管理员',
area: ['350px', '300px'],
content: 'AdminForm.jsp?type=1&id='+data.id+'&username='+data.username+'&password='+data.password+'&quanxian='+data.quanxian
});
}
});
var $ = layui.$, active = {
addAdmin: function(){ //获取选中数据
layer.open({
type: 2,
title:'添加管理员',
area: ['350px', '300px'],
content: 'AdminForm.jsp'
});
}
};
功能描述:
管理员管理已在商城入驻的商家,可以修改商家的全部信息,删除商家等
图3-28 商家管理页图
图3-29 商家管理修改页图
Controller层
@RequestMapping(value = "/data/getShop",produces = "application/json;charset=utf-8")
@ResponseBody
public PageBean getShop(@RequestParam(defaultValue = "1")Integer page, @RequestParam(defaultValue = "10")Integer limit)
{
if(check()) {
return shopsService.listShop(page, limit);
}
return null;
}
@RequestMapping(value = "/data/setShop",produces = "application/json;charset=utf-8")
@ResponseBody
public ResultInfo setShop(Shop shop)
{
if(check()) {
return shopsService.addOrUpdate(shop);
}
return new ResultInfo(false,"没有操作权限!");
}
@RequestMapping(value = "/data/delShop",produces = "application/json;charset=utf-8")
@ResponseBody
public ResultInfo delShop(Integer id)
{
if(check()) {
return shopsService.delete(id);
}
return new ResultInfo(false,"没有操作权限!");
}
@RequestMapping(value = "/data/ShopForm")
public String shopForm(Integer id)
{
if(check()) {
Shop shop = shopsService.findShopsBySid(id);
request.setAttribute("Shop",shop);
return "/admin/page/ShopForm.jsp";
}
return null;
}
Service层
public PageBean listShop(Integer pageNum, Integer pageSize)
{
Integer total = shopsDao.findShopCount();
Integer index = (pageNum - 1) * pageSize;
List<Shop> list = shopsDao.findShopByPage(index, pageSize);
return new PageBean(total, list, pageNum, pageSize);
}
public Shop findShopsBySid(Integer sid) {
return shopsDao.findBySid(sid);
}
功能描述:
管理员对商铺的管理,包括正在出租的商铺和已经营业的商铺。可以添加新商铺,修改商铺,删除商铺。
图3-30 商铺管理页图
图3-31 商铺管理修改页图
Controller层
@RequestMapping(value = "/data/getRent",produces = "application/json;charset=utf-8")
@ResponseBody
public PageBean getRent(@RequestParam(defaultValue = "1")Integer page, @RequestParam(defaultValue = "10")Integer limit)
{
if(check()) {
return shopsService.listRent(page, limit);
}
return null;
}
@RequestMapping(value = "/data/setRent",produces = "application/json;charset=utf-8")
@ResponseBody
public ResultInfo setRent(Shop shop)
{
if(check()) {
return shopsService.addOrUpdate(shop);
}
return new ResultInfo(false,"没有操作权限!");
}
@RequestMapping(value = "/data/delRent",produces = "application/json;charset=utf-8")
@ResponseBody
public ResultInfo delRent(Integer id)
{
if(check()) {
return shopsService.delete(id);
}
return new ResultInfo(false,"没有操作权限!");
}
@RequestMapping(value = "/data/RentForm")
public String rentForm(Integer id)
{
if(check()) {
Shop shop = shopsService.findShopsBySid(id);
request.setAttribute("Shop",shop);
return "/admin/page/RentForm.jsp";
}
return null;
}
前端
layui.use('table', function(){
var table = layui.table;
table.on('tool(demo)', function(obj){
var data = obj.data;
if(obj.event === 'del'){
layer.confirm('真的要删除吗?', function(index){
$.post('${pageContext.request.contextPath}/data/delRent?id='+data.sid, {}, function(str){
if(str.code === 0)
{
obj.del();
layer.alert("删除成功");
}else if(str.code === 1)
{
layer.alert("找不到数据,删除失败");
}else if(str.code === 2)
{
layer.alert("权限不足,删除失败");
}
});
layer.close(index);
});
} else if(obj.event === 'edit'){
layer.open({
type: 2,
title:'修改商铺',
area: ['100%', '100%'],
content: '${pageContext.request.contextPath}/data/RentForm?id='+data.sid
});
}
});
功能描述:
管理员对已经签订的合同的管理,可以查看合同,也可以删除合同。
图3-32 合同管理页图
图3-33 合同查看页图
Controller层
@RequestMapping(value = "/data/getCont",produces = "application/json;charset=utf-8")
@ResponseBody
public PageBean getCont(@RequestParam(defaultValue = "1")Integer page, @RequestParam(defaultValue = "10")Integer limit)
{
if(check()) {
return contractService.findByPage(page, limit);
}
return null;
}
@RequestMapping(value = "/data/setCont",produces = "application/json;charset=utf-8")
@ResponseBody
public ResultInfo setCont(Contract contract)
{
if(check()) {
return contractService.addOrUpdate(contract);
}
return new ResultInfo(false,"没有操作权限!");
}
@RequestMapping(value = "/data/delCont",produces = "application/json;charset=utf-8")
@ResponseBody
public ResultInfo delCont(String id)
{
if(check()) {
return contractService.delete(id);
}
return new ResultInfo(false,"没有操作权限!");
}
功能描述:
对网站首页轮播图的管理,可以添加和修改,删除轮播图,并且能够管理轮播图指向的链接,若指向链接为空,则不使用链接。可以选择上传本地的图片,也可以使用网络图片。
图3-34 首页轮播图管理页图
图3-35 首页轮播图修改页图
Controller层
@RequestMapping(value = "/data/getBanner",produces = "application/json;charset=utf-8")
@ResponseBody
public PageBean getBanner(@RequestParam(defaultValue = "1")Integer page, @RequestParam(defaultValue = "10")Integer limit)
{
if(check()) {
return bannerService.findByPage(page, limit);
}
return null;
}
@RequestMapping(value = "/data/setBanner",produces = "application/json;charset=utf-8")
@ResponseBody
public ResultInfo setBanner(Banner banner)
{
if(check()) {
return bannerService.addOrUpdate(banner);
}
return new ResultInfo(false,"没有操作权限!");
}
@RequestMapping(value = "/data/delBanner",produces = "application/json;charset=utf-8")
@ResponseBody
public ResultInfo delBanner(Integer id)
{
if(check()) {
return bannerService.delete(id);
}
return new ResultInfo(false,"没有操作权限!");
}
功能描述:
对商城活动和公告的管理,可以发布活动和公告招商等信息,信息将展示在首页显眼的位置。管理员可以添加新的活动和公告,修改活动和公告,删除活动和公告。
图3-36 公告和活动管理页图
图3-37 公告和活动修改页图
Controller层
@RequestMapping(value = "/data/getAnno",produces = "application/json;charset=utf-8")
@ResponseBody
public PageBean getAnno(@RequestParam(defaultValue = "1")Integer page, @RequestParam(defaultValue = "10")Integer limit)
{
if(check()) {
return announcementService.findByPage(page, limit);
}
return null;
}
@RequestMapping(value = "/data/setAnno",produces = "application/json;charset=utf-8")
@ResponseBody
public ResultInfo setAnno(Announcement announcement)
{
if(check()) {
return announcementService.addOrUpdate(announcement);
}
return new ResultInfo(false,"没有操作权限!");
}
@RequestMapping(value = "/data/delAnno",produces = "application/json;charset=utf-8")
@ResponseBody
public ResultInfo delAnno(Integer id)
{
if(check()) {
return announcementService.delete(id);
}
return new ResultInfo(false,"没有操作权限!");
}
@RequestMapping(value = "/data/AnnoForm")
public String annoForm(Integer id)
{
if(check()) {
Announcement announcement = announcementService.findById(id);
request.setAttribute("Anno",announcement);
return "/admin/page/AnnoForm.jsp";
}
return null;
}
事务就是把一系列的动作当成一个独立的工作单元,这些动作要么全部完成,要么全部不起作用。Spring中的事务分为编程式事务和声明式事务,本系统使用的是声明式事务,Spring也推荐使用声明式事务。声明式事务是将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。通过在xml文件中写一段配置实现,在需要使用事务时在指定方法或者类中添加@Transactional注解即可,方法执行之前启动事务,遇到运行时错误时自动回滚,没有遇到错误则提交事务。
XML配置
<aop:aspectj-autoproxy expose-proxy="true" />
<tx:annotation-driven transaction-manager="txManager" />
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="save*" propagation="REQUIRED" />
<tx:method name="add*" propagation="REQUIRED" />
<tx:method name="update*" propagation="REQUIRED" />
<tx:method name="del*" propagation="REQUIRED" />
<tx:method name="reg*" propagation="REQUIRED" />
<tx:method name="*" read-only="true" />
</tx:attributes>
</tx:advice>
<aop:config expose-proxy="true">
<aop:pointcut id="txPointcut"
expression="execution(* com.qiang.mall.service.*.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut" />
</aop:config>
用例编号 | 操作描述 | 预期结果 | 测试结果 |
1 | 启动系统进入登录界面,不输入任何信息 | 提示“用户名不能为空”信息 | 提示“用户名不能为空”信息 |
2 | 输入用户名,不输入密码,点击登录按钮 | 提示“密码不能为空”信息 | 提示“密码不能为空”信息 |
3 | 输入密码,不输入用户名,点击登录按钮 | 提示“用户名不能为空”信息 | 提示“用户名不能为空”信息 |
4 | 输入用户名和密码,点击登录按钮 | 提示“请输入验证码” | 提示“请输入验证码” |
5 | 输入用户名和密码,错误的验证码,点击登录按钮 | 提示“验证码错误” | 提示“验证码错误” |
6 | 输入用户名和密码,正确的验证码,点击登录按钮 | 登录成功并跳转至系统主页 | 登录成功并跳转至系统主页 |
表4-1 用户账号登录测试用例表
用例编号 | 操作描述 | 预期结果 | 测试结果 |
1 | 启动系统进入登录界面,不输入任何信息 | 提示“手机号不能为空”信息 | 提示“手机号不能为空”信息 |
2 | 输入手机号,不输入验证码,点击登录按钮 | 提示“验证码不能为空”信息 | 提示“验证码不能为空”信息 |
3 | 输入手机号,输入错误验证码,点击登录按钮 | 提示“验证码错误”信息 | 提示“验证码错误”信息 |
4 | 输入手机号,输入正确验证码,点击登录按钮 | 登录成功并跳转至系统主页 | 登录成功并跳转至系统主页 |
表4-2 用户手机登录测试用例表
测试内容 | 测试结果 |
入驻商家列表查看,商家详情页查看 | 通过 |
入驻商家留言评论评分 | 通过 |
租赁商铺列表查看,商铺详情页查看 | 通过 |
手写签字,在线签订租赁合同 | 通过 |
在线微信支付 | 通过 |
聊天大厅聊天 | 通过 |
管理员后台管理用户信息 | 通过 |
管理员后台管理入驻商家信息 | 通过 |
管理员后台管理租赁商铺信息 | 通过 |
管理员后台管理租赁合同信息 | 通过 |
管理员后台审核合同,审核过期商铺信息 | 通过 |
管理员发布和管理商城公告和活动信息 | 通过 |
管理员管理首页轮播图信息 | 通过 |
表4-3 功能测试表
至此,毕业设计全部完成,..........
源码下载地址:https://download.csdn.net/download/sunqiang4/21180240
打不开就是在审核