项目后端主要使用 springboot
数据库:mysql
使用mybaits操作数据库(xml方式)
缓存:redis
安全框架:shrio
前端:jq+bootstrap
数据库中密码字段使用 md5加密技术
前后端交互使用 ajax
thymeleaf模板引擎 显示某些变量 和引用相同代码
使用aop面向切面编程思想确定业务所需的的时间
项目主要分为 用户、商品(书籍)、购物车、订单 四大模块。
(1)、用户:用户的登录、注册、收货地址的增删改查和设置默认收货地址、头像的修改
(2)、商品:书籍的查询、展示到页面
(3)、购物车:添加到购物车 删除购物车 增加数量 提交购物车
(4)、订单:创建订单、查询订单
项目编写主要先从 DAO持久层–>Service业务层–>控制层–>前端的思路
编写业务层和控制层都需要先规划异常
所有实体类都继承于BaseEntity
BaseEntity有四个基本数据
用户类、地址类、区域类、商品类、购物车类、订单类、订单项类
区域类:
package com.atmae.store.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
/**
* 省市区的数据实体类
* @author Mae
*/
@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class District{
private Integer id;
private String parent;
private String code;
private String name;
}
项目需要对没有登录的用户进行拦截,即其不能访问除登录、注册以外的其他页面
package com.atmae.store.config;
import com.atmae.store.interceptor.LoginInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.ArrayList;
import java.util.List;
/**
* 处理器拦截器的注册
* @author Mae
*/
@Configuration
public class LoginInterceptorConfigurer implements WebMvcConfigurer {
@Override
/** 将自定义拦截器进行注册*/
public void addInterceptors(InterceptorRegistry registry) {
/** 自定义拦截器对象*/
HandlerInterceptor interceptor=new LoginInterceptor();
/** 配置白名单:存放在List集合中*/
List<String> patterns =new ArrayList<>();
patterns.add("/assets/**");
patterns.add("/users/login");
patterns.add("/users/register");
patterns.add("/register");
patterns.add("/login");
patterns.add("/index");
patterns.add("/district/**");
/** 完成拦截器的注册*/
/** 要拦截url /**: 表示项目下所有的请求被拦截*/
registry.addInterceptor(interceptor).addPathPatterns("/**")
.excludePathPatterns(patterns);
}
}
package com.atmae.store.interceptor;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author Mae
*/
public class LoginInterceptor implements HandlerInterceptor {
/**
* 检测全局Session是否有uid数据,如果有则放行,没有重定向到登录页面
*
* @param request 请求对象
* @param response 响应对象
* @param handler 处理器(url+Controller:映射)
* @return true表示放行当前的请求 false表示拦截当前的请求
* @throws Exception 异常
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Object user = request.getSession().getAttribute("userId");
if (user == null) {
/** 说明用户没有登录 则重定向到登陆页*/
response.sendRedirect("/login");
return false;
}
/** 表示放行*/
return true;
}
}
JsonResult类:主要在控制层以JSON方式响应前端数据
package com.atmae.store.util;
import java.io.Serializable;
/**
* Json格式的数据进行响应
*
* @author Mae
*/
public class JsonResult<E> implements Serializable {
/**
* 状态码
*/
private Integer state;
/**
* 描述信息
*/
private String message;
/**
* 数据
*/
private E data;
public JsonResult() {
}
public JsonResult(Integer state) {
this.state = state;
}
public JsonResult(Integer state, String message, E data) {
this.state = state;
this.message = message;
this.data = data;
}
public JsonResult(String message, E data) {
this.message = message;
this.data = data;
}
public JsonResult(Throwable e) {
this.message = e.getMessage();
}
public JsonResult(Integer state, E data) {
this.state = state;
this.data = data;
}
public Integer getState() {
return state;
}
public void setState(Integer state) {
this.state = state;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public E getData() {
return data;
}
public void setData(E data) {
this.data = data;
}
}
package com.atmae.store.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* value object 值对象
* 当进行select查询时 查询的结果数据时多张表中的内容,此时不能使用一个pojo实体类来接受
* 则重新建一个新的对象存储查询出来的结果对应的映射
* “ 多表操作 ”
* @author Mae
*/
@AllArgsConstructor
@NoArgsConstructor
@Data
public class CartVO implements Serializable {
private Integer userId;
private Integer cartId;
private Integer productId;
private Long price;
private Integer num;
private String title;
private String image;
private Long realPrice;
}
主要用来规划异常响应前端
package com.atmae.store.controller;
import com.atmae.store.controller.ex.*;
import com.atmae.store.service.ex.*;
import com.atmae.store.util.JsonResult;
import org.apache.tomcat.util.http.fileupload.FileUploadException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import javax.servlet.http.HttpSession;
/**
* 控制层类的基类
*
* @author Mae
*/
public class BaseController {
/**
* 操作成功的状态码
*/
public static final int OK = 200;
/**
* 请求处理方法
* 当前项目产生了异常 被统一拦截到此方法中,
* 这个方法就充当了请求处理的方法,方法的返回值直接返回到前端
*/
@ExceptionHandler(value = {ServiceException.class, FileUploadException.class}) //用于统一处理抛出的异常
public JsonResult<Void> handleException(Throwable e) {
JsonResult<Void> result = new JsonResult<>();
if (e instanceof UsernameDuplicatedException) {
result.setState(4000);
result.setMessage("用户名已经被占用");
} else if (e instanceof AddressCountLimitException) {
result.setState(4001);
result.setMessage("用户收货地址超出上限异常");
} else if (e instanceof AccessDeniedException) {
result.setState(4002);
result.setMessage("非法访问异常");
} else if (e instanceof AddressNotFoundException) {
result.setState(4003);
result.setMessage("收货地址没有发现异常");
} else if (e instanceof ProductNotFoundException) {
result.setState(4004);
result.setMessage("商品没有被发现异常");
} else if (e instanceof CartNotFoundException) {
result.setState(4005);
result.setMessage("购物车商品没有找到");
} else if (e instanceof InsertException) {
result.setState(5000);
result.setMessage("注册时产生未知的异常");
} else if (e instanceof UserNotFoundException) {
result.setState(5001);
result.setMessage("用户没有找到的异常");
} else if (e instanceof PasswordNotMatchException) {
result.setState(5002);
result.setMessage("用户名的密码不匹配的异常");
} else if (e instanceof UpdateException) {
result.setState(5003);
result.setMessage("更新数据时产生未知的异常");
} else if (e instanceof FileEmptyException) {
result.setState(6001);
result.setMessage("文件为空异常");
} else if (e instanceof FileSizeException) {
result.setState(6002);
result.setMessage("文件大小异常");
} else if (e instanceof FileStateException) {
result.setState(6003);
result.setMessage("文件状态异常");
} else if (e instanceof FileUploadIOException) {
result.setState(6004);
result.setMessage("文件加载IO异常");
} else if (e instanceof FileTypeException) {
result.setState(6005);
result.setMessage("文件类型异常");
}
return result;
}
/**
* 获取Session对象中的Uid
*
* @param session session对象
* @return 当前对象的Uid
*/
protected final Integer getUidFromSession(HttpSession session) {
return Integer.valueOf(session.getAttribute("userId")
.toString());
}
/**
* 获取Session对象的用户名
*
* @param session
* @return
*/
protected final String getUsernameFromSession(HttpSession session) {
return session.getAttribute("username").toString();
}
}
主要使用面向切面编程思想
package com.atmae.store.aop;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
/**
* @author Mae
* 将当前类的对象创建使用维护交由spring容器
* 将当前类标记为切面类
*/
@Aspect
@Component
@Slf4j
public class TimerAspect {
/**
* 映射到那个方法上
*/
@Around("execution(* com.atmae.store.service.impl.*.*(..))")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
/** 调用目标发发 login*/
Object result = pjp.proceed();
/** 后记录时间*/
long end = System.currentTimeMillis();
log.info("耗时" + (end - start) + "ms");
return result;
}
}
同登录页面差不多
首页的两列 新货和热销 主要是通过后台的查询商品操作 根据具体条件查询前四名
部分mapper文件:
<mapper namespace="com.atmae.store.mapper.ProductMapper">
<resultMap id="ProductEntityMap" type="com.atmae.store.entity.Product">
<id column="id" property="id"/>
<result column="category_id" property="categoryId"/>
<result column="item_type" property="itemType"/>
<result column="sell_point" property="sellPoint"/>
<result column="created_user" property="createdUser"/>
<result column="created_time" property="createdTime"/>
<result column="modified_user" property="modifiedUser"/>
<result column="modified_time" property="modifiedTime"/>
resultMap>
<select id="findHotList" resultMap="ProductEntityMap">
SELECT *
FROM t_book
WHERE status = 1
ORDER BY priority DESC LIMIT 0,4
select>
<select id="findNewList" resultMap="ProductEntityMap">
select *
from t_book
where status = 1
order by created_time desc limit 0,4
select>
部分页面代码:
使用ajax响应数据
<div class="row">
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<p class="panel-title">新到好货p>
div>
<div id="new-list" class="panel-body panel-item">
div>
div>
div>
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<p class="panel-title">热销排行p>
div>
<div id="hot-list" class="panel-body panel-item">
div>
div>
div>
div>
div>
div>
div>
<div th:replace="common::commonscipt">div>
<script type="text/javascript">
$(function () {
showNewList();
showHotList();
});
function showNewList() {
$("#new-list").empty();
$.ajax({
url: "/products/newList",
type: "GET",
dataType: "JSON",
success: function (json) {
let list = json.data;
console.log("count=" + list.length);
for (let i = 0; i < list.length; i++) {
console.log(list[i].title);
let html = '';
html = html.replace(/#{id}/g, list[i].id);
html = html.replace(/#{title}/g, list[i].title);
html = html.replace(/#{price}/g, list[i].price);
html = html.replace(/#{image}/g, list[i].image);
$("#new-list").append(html);
}
}
});
}
function showHotList() {
$("#hot-list").empty();
$.ajax({
url: "/products/hotList",
type: "GET",
dataType: "JSON",
success: function (json) {
let list = json.data;
console.log("count=" + list.length);
for (let i = 0; i < list.length; i++) {
console.log(list[i].title);
let html = '';
html = html.replace(/#{id}/g, list[i].id);
html = html.replace(/#{title}/g, list[i].title);
html = html.replace(/#{price}/g, list[i].price);
html = html.replace(/#{image}/g, list[i].image);
$("#hot-list").append(html);
}
}
});
}
script>
$.ajax({
url: "/users/getByUserId",
data: $("#form-date").serialize(),
type: "GET",
dataType: "JSON",
success: function (json) {
if (json.state === 200) {
//将查询到的数据重新设置到控件中
$("#phone").val(json.data.phone);
$("#email").val(json.data.email);
if (json.data.gender === 0) {
let radio = $("#gender-female");
radio.prop("checked", "checked");
} else if (json.data.gender === 1) {
let radio = $("#gender-male");
radio.prop("checked", "checked");
}
// prop:给某个元素添加属性及属性值!
} else {
alert("数据不存在!");
}
},
error: function (xhr) {
alert("查询用户信息时产生未知的异常!" + xhr.status);
}
});
return false;
});
/**
* 上传文件的最大值
*/
private static final long AVATAR_MAX_SIZE = 10 * 1024 * 1024;
/**
* 限制上传文件的类型
*/
private static final List<String> AVATAR_TYPE = new ArrayList<>();
/** 静态块给集合初始化*/
static {
AVATAR_TYPE.add("image/png");
AVATAR_TYPE.add("image/jpeg");
AVATAR_TYPE.add("image/bmp");
AVATAR_TYPE.add("image/gif");
}
@RequestMapping("/changeAvatar")
public JsonResult<String> changeAvatar(HttpServletResponse response, HttpSession session, MultipartFile file) throws FileNotFoundException {
/** 判断文件是否为空*/
if (file.isEmpty()) {
throw new FileEmptyException("文件为空");
}
/** 判断文件是否超出大小*/
if (file.getSize() > AVATAR_MAX_SIZE) {
throw new FileSizeException("文件超出限制");
}
/** 判断文件类型是否是规定的和后缀类型*/
String contentType = file.getContentType();
/** 如何集合包含某个元素则返回true*/
if (!AVATAR_TYPE.contains(contentType)) {
throw new FileTypeException("文件类型不支持");
}
/** 上传的文件放在 .../assets/img/avatar/ .(后缀)下*/
String property = System.getProperty("user.dir");
String path = property + "\\store\\src\\main\\resources\\static\\assets\\img\\avatar";
/** File对象指向这个路径,File是否存在*/
File dir = new File(path);
/** 检测目录是否存在*/
if (!dir.exists()) {
/** 创建当前的目录*/
dir.mkdirs();
}
/** 获取到这个文件名称 UUID工具来将生成一个新的字符串作为文件名*/
String originalFilename = file.getOriginalFilename();
/** 文件后缀*/
int index = originalFilename.lastIndexOf(".");
String suffix = originalFilename.substring(index);
String filename = UUID.randomUUID().toString().toUpperCase() + suffix;
/** 这是一个空文件*/
File dest = new File(dir, filename);
/** 参数file中数据写入到这个空文件中*/
try {
/** 将file文件中的数据写入到dest文件中*/
file.transferTo(dest);
} catch (IOException e) {
throw new FileUploadIOException("文件读写异常");
} catch (FileStateException e) {
throw new FileSizeException("文件状态异常");
}
Integer userId = getUidFromSession(session);
String username = getUsernameFromSession(session);
/** 返回头像的路径*/
String avatar = "assets/img/avatar/" + filename;
userService.changeAvatar(userId, avatar, username);
/** 存储cookie 响应给客户端*/
Cookie cookie = new Cookie("img", avatar);
response.addCookie(cookie);
/** 返回用户头像的路径给前端页面,将来用于头像展示使用*/
return new JsonResult<>(OK, avatar);
}
将头像存储到cookie中,而用户的id和用户名存储在session中
头像会存放在服务器中,数据库中存放的是头像当前项目的相对地址
可以设置默认收货地址和增加收货地址
页面的省市区通过ajax方式加载
没有选择大的区域则不显示小区域
$(function () {
/*value 属性用于表示当前区域的code值*/
let defaultOption = "";
showProvinceList();
$("#city-list").append(defaultOption);
$("#area-list").append(defaultOption);
/** change():监听某个控件一旦发生改变 就会触发*/
$("#province-list").change(function () {
// 获取行政区的父代码
let parent = $("#province-list").val();
// 清空select下拉列表的所有 option元素
$("#city-list").empty();
$("#area-list").empty();
//填充默认值
$("#city-list").append(defaultOption);
$("#area-list").append(defaultOption);
if (parent === 0) {
return;
}
$.ajax({
url: "/district",
type: "POST",
data: "parent=" + parent,
dataType: "JSON",
success: function (json) {
if (json.state === 200) {
let list = json.data;
for (let i = 0; i < list.length; i++) {
let opt = " + list[i].name + "";
$("#city-list").append(opt);
}
} else {
alert("信息加载失败!");
}
}
});
});
$("#city-list").change(function () {
// 获取行政区的父代码
let parent = $("#city-list").val();
// 清空select下拉列表的所有 option元素
$("#area-list").empty();
//填充默认值
$("#area-list").append(defaultOption);
if (parent === 0) {
return;
}
$.ajax({
url: "/district",
type: "POST",
data: "parent=" + parent,
dataType: "JSON",
success: function (json) {
if (json.state === 200) {
let list = json.data;
for (let i = 0; i < list.length; i++) {
let opt = " + list[i].name + "";
$("#area-list").append(opt);
}
} else {
alert("信息加载失败!");
}
}
});
});
function showProvinceList() {
$.ajax({
url: "/district",
type: "POST",
data: "parent=86",
dataType: "JSON",
success: function (json) {
if (json.state === 200) {
let list = json.data;
for (let i = 0; i < list.length; i++) {
let opt = " + list[i].name + "";
$("#province-list").append(opt);
}
} else {
alert("信息加载失败!");
}
}
});
return false;
}
$("#btn-add-new-address").click(function () {
$.ajax({
url: "/address/addNewAddress",
type: "POST",
data: $("#form-add-new-address").serialize(),
dataType: "JSON",
success: function (json) {
if (json.state === 200) {
alert("新增收货地址成功!");
location.href = "/addAddress";
} else {
alert("新增收货地址失败!");
}
},
error: function (xhr) {
alert("新增收货地址产生未知的异常!" + xhr.status);
}
});
return false;
});
});
纸上得来终觉浅
用来练手,很好的复习了学过的知识