一、主要技术
项目后端主要使用 springboot 数据库:mysql 使用mybaits操作数据库(xml方式) 缓存:redis 安全框架:shrio 前端:jq+bootstrap 数据库中密码字段使用 md5加密技术 前后端交互使用 ajax thymeleaf模板引擎 显示某些变量 和引用相同代码 使用aop面向切面编程思想确定业务所需的的时间
二、项目描述
1、项目目录
2、项目思路
项目主要分为 用户、商品(书籍)、购物车、订单 四大模块。 (1)、用户:用户的登录、注册、收货地址的增删改查和设置默认收货地址、头像的修改 (2)、商品:书籍的查询、展示到页面 (3)、购物车:添加到购物车 删除购物车 增加数量 提交购物车 (4)、订单:创建订单、查询订单
项目编写主要先从 DAO持久层–>Service业务层–>控制层–>前端的思路 编写业务层和控制层都需要先规划异常
三、项目部分类
1、pojo实体类
所有实体类都继承于BaseEntity BaseEntity有四个基本数据
用户类、地址类、区域类、商品类、购物车类、订单类、订单项类 区域类:
package com.atmae.store.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class District{
private Integer id;
private String parent;
private String code;
private String name;
}
对应的数据库数据(3523条): 主要用来动态获得省市区
2、拦截器
项目需要对没有登录的用户进行拦截,即其不能访问除登录、注册以外的其他页面
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;
@Configuration
public class LoginInterceptorConfigurer implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
HandlerInterceptor interceptor=new LoginInterceptor();
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/**");
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;
public class LoginInterceptor implements HandlerInterceptor {
@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;
}
}
3、Util工具类
JsonResult类:主要在控制层以JSON方式响应前端数据
package com.atmae.store.util;
import java.io.Serializable;
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;
}
}
4、VO层
package com.atmae.store.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@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;
}
5、控制层的基类
主要用来规划异常响应前端
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;
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;
}
protected final Integer getUidFromSession(HttpSession session) {
return Integer.valueOf(session.getAttribute("userId")
.toString());
}
protected final String getUsernameFromSession(HttpSession session) {
return session.getAttribute("username").toString();
}
}
6、业务时间计算
主要使用面向切面编程思想
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;
@Aspect
@Component
@Slf4j
public class TimerAspect {
@Around("execution(* com.atmae.store.service.impl.*.*(..))")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
Object result = pjp.proceed();
long end = System.currentTimeMillis();
log.info("耗时" + (end - start) + "ms");
return result;
}
}
日志打印
四、功能演示
1、登录
登录页面:
2、注册页面
同登录页面差不多
3、首页:
首页的两列 新货和热销 主要是通过后台的查询商品操作 根据具体条件查询前四名 部分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 = '<div class="col-md-12">'
+ '<div class="col-md-7 text-row-2"><a href="/product?id=#{id}">#{title}</a></div>'
+ '<div class="col-md-2">¥#{price}</div>'
+ '<div class="col-md-3"><img src="assets/#{image}collect.png" class="img-responsive" /></div>'
+ '</div>';
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 = '<div class="col-md-12">'
+ '<div class="col-md-7 text-row-2"><a href="/product?id=#{id}">#{title}</a></div>'
+ '<div class="col-md-2">¥#{price}</div>'
+ '<div class="col-md-3"><img src="assets/#{image}collect.png" class="img-responsive" /></div>'
+ '</div>';
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>
4、个人基本信息
加载页面即显示数据:ajax实现.
$.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");
}
} else {
alert("数据不存在!");
}
},
error: function (xhr) {
alert("查询用户信息时产生未知的异常!" + xhr.status);
}
});
return false;
});
5、头像修改
控制层代码:
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();
if (!AVATAR_TYPE.contains(contentType)) {
throw new FileTypeException("文件类型不支持");
}
String property = System.getProperty("user.dir");
String path = property + "\\store\\src\\main\\resources\\static\\assets\\img\\avatar";
File dir = new File(path);
if (!dir.exists()) {
dir.mkdirs();
}
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);
try {
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 = new Cookie("img", avatar);
response.addCookie(cookie);
return new JsonResult<>(OK, avatar);
}
将头像存储到cookie中,而用户的id和用户名存储在session中 头像会存放在服务器中,数据库中存放的是头像当前项目的相对地址
6、地址信息
可以设置默认收货地址和增加收货地址 页面的省市区通过ajax方式加载
没有选择大的区域则不显示小区域
$(function () {
let defaultOption = "<option value='0'>---- 请选择 ----</option>";
showProvinceList();
$("#city-list").append(defaultOption);
$("#area-list").append(defaultOption);
$("#province-list").change(function () {
let parent = $("#province-list").val();
$("#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 = "<option value='" + list[i].code + "'>" + list[i].name + "</option>";
$("#city-list").append(opt);
}
} else {
alert("信息加载失败!");
}
}
});
});
$("#city-list").change(function () {
let parent = $("#city-list").val();
$("#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 = "<option value='" + list[i].code + "'>" + list[i].name + "</option>";
$("#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 = "<option value='" + list[i].code + "'>" + list[i].name + "</option>";
$("#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;
});
});
7、购物页面
查询所有书籍即可
8、购物车
也可以增加数量和删除
9、结算
五、总结
纸上得来终觉浅 用来练手,很好的复习了学过的知识
|