1. 用户注册
1.1 用户名登录注册流程
1.2 邮箱注册流程
1.3 手机号注册
2.检验用户名是否存在


<!-- apache 工具类 --><!-- 文件检测 --><!-- https://mvnrepository.com/artifact/org.apache.tika/tika-core --><dependency><groupId>org.apache.tika</groupId><artifactId>tika-core</artifactId><version>2.2.1</version></dependency><!-- https://mvnrepository.com/artifact/commons-codec/commons-codec --><dependency><groupId>commons-codec</groupId><artifactId>commons-codec</artifactId><version>1.15</version></dependency><!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 --><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.12.0</version></dependency><!-- https://mvnrepository.com/artifact/commons-io/commons-io --><dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>2.11.0</version></dependency>
3. 状态码修改


4. 使用自定义响应数据类型
https://www.yuque.com/meishimokeqide/myi2rx/gahnvo/edit
5. 接受前端注册消息
我们统一将前端传过来的信息定义为xxxBO类


加密,设置时间,全局唯一id




https://www.yuque.com/meishimokeqide/myi2rx/iugpp7
6. 实现登录功能
7. 页面显示信息,使用Cookie
7.1 CookieUtils
package com.lv.utils;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import javax.servlet.http.Cookie;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.UnsupportedEncodingException;import java.net.URLDecoder;import java.net.URLEncoder;/**** @Title: CookieUtils.java* @Package com.lv.utils* @Description: Cookie 工具类* Copyright: Copyright (c)* Company: www.lv.com** @author imooc* @version V1.0*/public final class{final static Logger logger = LoggerFactory.getLogger(CookieUtils.class);/**** @Description: 得到Cookie的值, 不编码* @param request* @param cookieName* @return*/public static String getCookieValue(HttpServletRequest request, String cookieName) {return getCookieValue(request, cookieName, false);}/**** @Description: 得到Cookie的值* @param request* @param cookieName* @param isDecoder* @return*/public static String getCookieValue(HttpServletRequest request, String cookieName, boolean isDecoder) {Cookie[] cookieList = request.getCookies();if (cookieList == null || cookieName == null) {return null;}String retValue = null;try {for (int i = 0; i < cookieList.length; i++) {if (cookieList[i].getName().equals(cookieName)) {if (isDecoder) {retValue = URLDecoder.decode(cookieList[i].getValue(), "UTF-8");} else {retValue = cookieList[i].getValue();}break;}}} catch (UnsupportedEncodingException e) {e.printStackTrace();}return retValue;}/**** @Description: 得到Cookie的值* @param request* @param cookieName* @param encodeString* @return*/public static String getCookieValue(HttpServletRequest request, String cookieName, String encodeString) {Cookie[] cookieList = request.getCookies();if (cookieList == null || cookieName == null) {return null;}String retValue = null;try {for (int i = 0; i < cookieList.length; i++) {if (cookieList[i].getName().equals(cookieName)) {retValue = URLDecoder.decode(cookieList[i].getValue(), encodeString);break;}}} catch (UnsupportedEncodingException e) {e.printStackTrace();}return retValue;}/**** @Description: 设置Cookie的值 不设置生效时间默认浏览器关闭即失效,也不编码* @param request* @param response* @param cookieName* @param cookieValue*/public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,String cookieValue) {setCookie(request, response, cookieName, cookieValue, -1);}/**** @Description: 设置Cookie的值 在指定时间内生效,但不编码* @param request* @param response* @param cookieName* @param cookieValue* @param cookieMaxage*/public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,String cookieValue, int cookieMaxage) {setCookie(request, response, cookieName, cookieValue, cookieMaxage, false);}/**** @Description: 设置Cookie的值 不设置生效时间,但编码* 在服务器被创建,返回给客户端,并且保存客户端* 如果设置了SETMAXAGE(int seconds),会把cookie保存在客户端的硬盘中* 如果没有设置,会默认把cookie保存在浏览器的内存中* 一旦设置setPath():只能通过设置的路径才能获取到当前的cookie信息* @param request* @param response* @param cookieName* @param cookieValue* @param isEncode*/public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,String cookieValue, boolean isEncode) {setCookie(request, response, cookieName, cookieValue, -1, isEncode);}/**** @Description: 设置Cookie的值 在指定时间内生效, 编码参数* @param request* @param response* @param cookieName* @param cookieValue* @param cookieMaxage* @param isEncode*/public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,String cookieValue, int cookieMaxage, boolean isEncode) {doSetCookie(request, response, cookieName, cookieValue, cookieMaxage, isEncode);}/**** @Description: 设置Cookie的值 在指定时间内生效, 编码参数(指定编码)* @param request* @param response* @param cookieName* @param cookieValue* @param cookieMaxage* @param encodeString*/public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,String cookieValue, int cookieMaxage, String encodeString) {doSetCookie(request, response, cookieName, cookieValue, cookieMaxage, encodeString);}/**** @Description: 删除Cookie带cookie域名* @param request* @param response* @param cookieName*/public static void deleteCookie(HttpServletRequest request, HttpServletResponse response,String cookieName) {doSetCookie(request, response, cookieName, null, -1, false);// doSetCookie(request, response, cookieName, "", -1, false);}/**** @Description: 设置Cookie的值,并使其在指定时间内生效* @param request* @param response* @param cookieName* @param cookieValue* @param cookieMaxage cookie生效的最大秒数* @param isEncode*/private static final void doSetCookie(HttpServletRequest request, HttpServletResponse response,String cookieName, String cookieValue, int cookieMaxage, boolean isEncode) {try {if (cookieValue == null) {cookieValue = "";} else if (isEncode) {cookieValue = URLEncoder.encode(cookieValue, "utf-8");}Cookie cookie = new Cookie(cookieName, cookieValue);if (cookieMaxage > 0)cookie.setMaxAge(cookieMaxage);if (null != request) {// 设置域名的cookieString domainName = getDomainName(request);logger.info("========== domainName: {} ==========", domainName);if (!"localhost".equals(domainName)) {cookie.setDomain(domainName);}}cookie.setPath("/");response.addCookie(cookie);} catch (Exception e) {e.printStackTrace();}}/**** @Description: 设置Cookie的值,并使其在指定时间内生效* @param request* @param response* @param cookieName* @param cookieValue* @param cookieMaxage cookie生效的最大秒数* @param encodeString*/private static final void doSetCookie(HttpServletRequest request, HttpServletResponse response,String cookieName, String cookieValue, int cookieMaxage, String encodeString) {try {if (cookieValue == null) {cookieValue = "";} else {cookieValue = URLEncoder.encode(cookieValue, encodeString);}Cookie cookie = new Cookie(cookieName, cookieValue);if (cookieMaxage > 0)cookie.setMaxAge(cookieMaxage);if (null != request) {// 设置域名的cookieString domainName = getDomainName(request);logger.info("========== domainName: {} ==========", domainName);if (!"localhost".equals(domainName)) {cookie.setDomain(domainName);}}cookie.setPath("/");response.addCookie(cookie);} catch (Exception e) {e.printStackTrace();}}/**** @Description: 得到cookie的域名* @return*/private static final String getDomainName(HttpServletRequest request) {String domainName = null;String serverName = request.getRequestURL().toString();if (serverName == null || serverName.equals("")) {domainName = "";} else {serverName = serverName.toLowerCase();serverName = serverName.substring(7);final int end = serverName.indexOf("/");serverName = serverName.substring(0, end);if (serverName.indexOf(":") > 0) {String[] ary = serverName.split("\\:");serverName = ary[0];}final String[] domains = serverName.split("\\.");int len = domains.length;if (len > 3 && !isIp(serverName)) {// www.xxx.com.cndomainName = "." + domains[len - 3] + "." + domains[len - 2] + "." + domains[len - 1];} else if (len <= 3 && len > 1) {// xxx.com or xxx.cndomainName = "." + domains[len - 2] + "." + domains[len - 1];} else {domainName = serverName;}}return domainName;}public static String trimSpaces(String IP){//去掉IP字符串前后所有的空格while(IP.startsWith(" ")){IP= IP.substring(1,IP.length()).trim();}while(IP.endsWith(" ")){IP= IP.substring(0,IP.length()-1).trim();}return IP;}public static boolean isIp(String IP){//判断是否是一个IPboolean b = false;IP = trimSpaces(IP);if(IP.matches("\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}")){String s[] = IP.split("\\.");if(Integer.parseInt(s[0])<255)if(Integer.parseInt(s[1])<255)if(Integer.parseInt(s[2])<255)if(Integer.parseInt(s[3])<255)b = true;}return b;}}
7.2 JsonUtils
package com.lv.utils;import com.fasterxml.jackson.core.JsonProcessingException;import com.fasterxml.jackson.databind.JavaType;import com.fasterxml.jackson.databind.ObjectMapper;import java.util.List;/**** @Title: JsonUtils.java* @Package com.lv.utils* @Description: json转换类* Copyright: Copyright (c)* Company: www.lv.com** @author imooc*/public class JsonUtils {// 定义jackson对象private static final ObjectMapper MAPPER = new ObjectMapper();/*** 将对象转换成json字符串。* @param data* @return*/public static String objectToJson(Object data) {try {return MAPPER.writeValueAsString(data);} catch (JsonProcessingException e) {e.printStackTrace();}return null;}/*** 将json结果集转化为对象** @param jsonData json数据* @param beanType 对象中的object类型* @return*/public static <T> T jsonToPojo(String jsonData, Class<T> beanType) {try {T t = MAPPER.readValue(jsonData, beanType);return t;} catch (Exception e) {e.printStackTrace();}return null;}/*** 将json数据转换成pojo对象list* @param jsonData* @param beanType* @return*/public static <T>List<T> jsonToList(String jsonData, Class<T> beanType) {JavaType javaType = MAPPER.getTypeFactory().constructParametricType(List.class, beanType);try {List<T> list = MAPPER.readValue(jsonData, javaType);return list;} catch (Exception e) {e.printStackTrace();}return null;}}

前后端对应
在注册后也将信息保存到cookie
8 用户退出,清除信息
9. 前端轮播图
创建servicel和serviceImpl
对固定的数据创建枚举类

创建controller
10. 首页分类需求分析
10.1 查询一级分类
10.2通过自连接查询二级分类

10.3 通过自定义Mapper实现查询子分类
1.自定义mapper类
我们这里需要多表联查所以单体查询已经不能满足需求,要创建自定义mapper
package com.lv.mapper;import com.lv.pojo.vo.CategoryVO;import com.lv.pojo.vo.NewItemsVO;import org.apache.ibatis.annotations.Mapper;import org.apache.ibatis.annotations.Param;import java.util.List;import java.util.Map;/*** @author ziming.xing* Create Date:2021/5/18*/@Mapperpublic interface CategoryCustomMapper {List<CategoryVO> getSubCatList(@Param("rootCatId") Integer rootCatId);List<NewItemsVO> getSixNewItemLazy(@Param("paramsMap") Map<String, Object> paramsMap);}
2.创建vo包,之前的bo是前端传给后端的数据,这个vo是后端传给前端,前端view上显示的数据


3.创建mybatis的xml
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.lv.mapper.CategoryCustomMapper"><!--collection 标签:用于定义关联的list集合类型的封装规则property:对应三级分类的list属性名ofType:集合的类型,三级分类的VO--><resultMap id="MyCategoryVO" type="com.lv.pojo.vo.CategoryVO"><id column="id" jdbcType="INTEGER" property="id"/><result column="name" jdbcType="VARCHAR" property="name"/><result column="type" jdbcType="INTEGER" property="type"/><result column="father_id" jdbcType="INTEGER" property="fatherId"/><collection property="subCatList" ofType="com.lv.pojo.vo.SubCategoryVO"><id column="sub_id" jdbcType="INTEGER" property="subId"/><result column="sub_name" jdbcType="VARCHAR" property="subName"/><result column="sub_type" jdbcType="INTEGER" property="subType"/><result column="sub_father_id" jdbcType="INTEGER" property="subFatherId"/></collection></resultMap><select id="getSubCatList" resultMap="MyCategoryVO">SELECT f.id,f.`name`,f.type,f.father_id,c.id AS sub_id,c.`name` AS sub_name,c.type AS sub_type,c.father_id AS sub_father_idFROM `category` AS fLEFT JOIN `category` AS c ON f.id = c.father_idWHERE f.father_id = #{rootCatId,jdbcType=INTEGER}</select></mapper>
10.4.首页商品推荐

<resultMap id="MyNewItemsVO" type="com.lv.pojo.vo.NewItemsVO"><id column="root_cat_id" jdbcType="INTEGER" property="rootCatId"/><result column="root_cat_name" jdbcType="VARCHAR" property="rootCatName"/><result column="slogan" jdbcType="VARCHAR" property="slogan"/><result column="cat_image" jdbcType="VARCHAR" property="catImage"/><result column="bg_color" jdbcType="VARCHAR" property="bgColor"/><collection property="simpleItemList" ofType="cn.myst.web.pojo.vo.SimpleItemVO"><result column="item_id" jdbcType="VARCHAR" property="itemId"/><result column="item_name" jdbcType="VARCHAR" property="itemName"/><result column="item_url" jdbcType="VARCHAR" property="itemUrl"/></collection></resultMap><select id="getSixNewItemLazy" resultMap="MyNewItemsVO">SELECT f.id AS root_cat_id,f.`name` AS root_cat_name,f.slogan,f.cat_image,f.bg_color,i.id AS item_id,i.item_name AS item_name,ii.url AS item_url,i.created_timeFROM category fLEFT JOIN items i ON f.id = i.root_cat_idLEFT JOIN items_img ii ON i.id = ii.item_idWHERE f.type = 1AND i.root_cat_id = #{paramsMap.rootCatId}AND ii.is_main = 1ORDER BY i.created_time DESCLIMIT 0,6</select>
11.商品详情功能分析
11.1 搜索信息
11.2 编写相关查询servcie
我们查询都是和商品相关的,并且都是单表查询,所以我们使用通用mapper就可以

创建一个vo将数据传给前端
12.商品的评价
12.1 查询商品评价等级数量
创建评价vo
、
创建评价枚举
service层,查询评价等级和各个等级数量,在做和
controller层


12.2 自定义mapper查询
因为涉及到多表关联,通用mapper不能完成
- 数据库设计

- mapper层


- service层
12.3 mybatis分页插件
引入相关依赖,设置yml


- controller层

创建一个BaseController

12.4 信息脱敏

/*** 通用脱敏工具类* 可用于:* 用户名* 手机号* 邮箱* 地址等*/public class DesensitizationUtil {private static final int SIZE = 6;private static final String SYMBOL = "*";/*** 通用脱敏方法* @param value* @return*/public static String commonDisplay(String value) {if (null == value || "".equals(value)) {return value;}int len = value.length();int pamaone = len / 2;int pamatwo = pamaone - 1;int pamathree = len % 2;StringBuilder stringBuilder = new StringBuilder();if (len <= 2) {if (pamathree == 1) {return SYMBOL;}stringBuilder.append(SYMBOL);stringBuilder.append(value.charAt(len - 1));} else {if (pamatwo <= 0) {stringBuilder.append(value.substring(0, 1));stringBuilder.append(SYMBOL);stringBuilder.append(value.substring(len - 1, len));} else if (pamatwo >= SIZE / 2 && SIZE + 1 != len) {int pamafive = (len - SIZE) / 2;stringBuilder.append(value.substring(0, pamafive));for (int i = 0; i < SIZE; i++) {stringBuilder.append(SYMBOL);}if ((pamathree == 0 && SIZE / 2 == 0) || (pamathree != 0 && SIZE % 2 != 0)) {stringBuilder.append(value.substring(len - pamafive, len));} else {stringBuilder.append(value.substring(len - (pamafive + 1), len));}} else {int pamafour = len - 2;stringBuilder.append(value.substring(0, 1));for (int i = 0; i < pamafour; i++) {stringBuilder.append(SYMBOL);}stringBuilder.append(value.substring(len - 1, len));}}return stringBuilder.toString();}}
13. 商品搜索功能
13.1主页搜索框关键字商品搜索
- 会用到多表关联,也会用到分页操作

sql语句 ```sql SELECT i.id as itemId, i.item_name as itemName, i.sell_counts as sellCounts, ii.url as imgUrl, tempSpec.price_discount as price FROM items i LEFT JOIN items_img ii ON i.id=ii.item_id LEFT JOIN (
SELECTitem_id,MIN(price_discount) as price_discountFROMitems_specGROUP BYitem_id
) tempSpec ON i.id =tempSpec.item_id WHERE ii.is_main =1;
xml```xml<!-- k:默认,代表默认排序,根据name--><!-- c:根据销量排序--><!-- p:根据价格排序--><!-- --><select id="searchItems" parameterType="Map" resultType="com.lv.pojo.vo.SearchItemsVO">SELECTi.id as itemId,i.item_name as itemName,i.sell_counts as sellCounts,ii.url as imgUrl,tempSpec.price_discount as priceFROMitems iLEFT JOINitems_img iiONi.id=ii.item_idLEFT JOIN( SELECT item_id,MIN(price_discount) as price_discount FROM items_spec GROUP BY item_id ) tempSpecONi.id =tempSpec.item_idWHEREii.is_main =1;<if test="paramsMap.keywords !=null and paramsMap.keywords !='' ">AND i.item_name LIKE '%${paramsMap.keywords}%'</if>ORDER BY<choose><when test="paramsMap.sort=="c"">i.sell_counts desc</when><when test="paramsMap.sort=="p"">tempSpec.price_discount asc</when><otherwise>i.item_name asc</otherwise></choose></select>
多表联查,要用到自定义mapper和VO
mapper
service
controller
13.2 前端业务与商品分类搜索
通过主页的三级分类id,进行搜索
<select id="searchItemsByThirdCat" parameterType="Map" resultType="com.lv.pojo.vo.SearchItemsVO">SELECTi.id as itemId,i.item_name as itemName,i.sell_counts as sellCounts,ii.url as imgUrl,tempSpec.price_discount as priceFROMitems iLEFT JOINitems_img iiONi.id=ii.item_idLEFT JOIN( SELECT item_id,MIN(price_discount) as price_discount FROM items_spec GROUP BY item_id ) tempSpecONi.id =tempSpec.item_idWHEREii.is_main =1ANDi.cat_id = #{paramsMap.catId}ORDER BY<choose><when test="paramsMap.sort=="c"">i.sell_counts desc</when><when test="paramsMap.sort=="p"">tempSpec.price_discount asc</when><otherwise>i.item_name asc</otherwise></choose></select>
14.购物车
14.1 购物车存储形式
使用Cookie和redis一起做,同时我们可以仿造京东购物车


14.2 使用Cookie和Redis,编写未登录和已登录加入购物车的代码
14.3 渲染(刷新)购物车
不能直接使用Cookie中的信息,因为有可能商品的信息,价格发生了更改,我们需要通过商品规格id重新刷新一下购物车信息。
- 要使用到多表关联,我们要用自定义mapper和数据层的VO,封装购物车信息

mapper.xml ```xml
- **自定义mapper**- **service和serviceImpl**<br />- **controller**<a name="fpFQe"></a>## 14.4 其他业务操作删除操作<a name="wKXlm"></a># 15.收货地址<a name="pDcFe"></a>## 15.1 需求分析- 进入页面后,需要将所有的收货地址列出来- 可以新增地址- 可以修改之前的地址- 可以设置为默认:默认会被选中<a name="G6QNI"></a>## 15.2 表设计- 关联用户 ID:一个用户会有多个收货地址- 扩展字段:可能会有详细地址 2用户在确认订单页面,可以针对收货地址做如下操作:1. 查询用户的所有收货地址列表2. 新增收货地址3. 删除收货地址4. 修改收货地址5. 设置默认地址另外在编辑地址时,所需要的省市区信息的维护:本课程是放在前端 js 中维护的,这种信息一般半年才维护一次,变动不太大。<a name="qv42K"></a>## 15.3 查询用户的所有收货地址列表controller```java@ApiOperation(value = "根据用户id查询收货地址列表", notes = "根据用户id查询收货地址列表", httpMethod = "POST")@PostMapping("/list")public JSONResult list(@RequestParam String userId) {if (StringUtils.isBlank(userId)) {return JSONResult.errorMsg("");}List<UserAddress> list = addressService.queryAll(userId);return JSONResult.ok(list);}
service
@Transactional(propagation = Propagation.SUPPORTS)@Overridepublic List<UserAddress> queryAll(String userId) {UserAddress ua = new UserAddress();ua.setUserId(userId);return userAddressMapper.select(ua);}
15.4 新增收获地址
@ApiOperation(value = "用户新增地址", notes = "用户新增地址", httpMethod = "POST")@PostMapping("/add")public JSONResult add(@RequestBody AddressBO addressBO) {JSONResult checkRes = checkAddress(addressBO);if (checkRes.getStatus() != 200) {return checkRes;}addressService.addNewUserAddress(addressBO);return JSONResult.ok();}private JSONResult checkAddress(AddressBO addressBO) {String receiver = addressBO.getReceiver();if (StringUtils.isBlank(receiver)) {return JSONResult.errorMsg("收货人不能为空");}if (receiver.length() > 12) {return JSONResult.errorMsg("收货人姓名不能太长");}String mobile = addressBO.getMobile();if (StringUtils.isBlank(mobile)) {return JSONResult.errorMsg("收货人手机号不能为空");}if (mobile.length() != 11) {return JSONResult.errorMsg("收货人手机号长度不正确");}boolean isMobileOk = MobileEmailUtils.checkMobileIsOk(mobile);if (!isMobileOk) {return JSONResult.errorMsg("收货人手机号格式不正确");}String province = addressBO.getProvince();String city = addressBO.getCity();String district = addressBO.getDistrict();String detail = addressBO.getDetail();if (StringUtils.isBlank(province) ||StringUtils.isBlank(city) ||StringUtils.isBlank(district) ||StringUtils.isBlank(detail)) {return JSONResult.errorMsg("收货地址信息不能为空");}return JSONResult.ok();}
Bo
/*** 用户新增或修改地址的 BO*/public class AddressBO {private String addressId;private String userId;private String receiver;private String mobile;private String province;private String city;private String district;private String detail;
service
@Transactional(propagation = Propagation.REQUIRED)@Overridepublic void addNewUserAddress(AddressBO addressBO) {// 1. 判断当前用户是否存在地址,如果没有,则新增为‘默认地址’Integer isDefault = 0;List<UserAddress> addressList = this.queryAll(addressBO.getUserId());if (addressList == null || addressList.isEmpty() || addressList.size() == 0) {isDefault = 1;}String addressId = sid.nextShort();// 2. 保存地址到数据库UserAddress newAddress = new UserAddress();BeanUtils.copyProperties(addressBO, newAddress);newAddress.setId(addressId);newAddress.setIsDefault(isDefault);newAddress.setCreatedTime(new Date());newAddress.setUpdatedTime(new Date());userAddressMapper.insert(newAddress);}
15.5 删除收货地址
controller
@ApiOperation(value = "用户删除地址", notes = "用户删除地址", httpMethod = "POST")@PostMapping("/delete")public JSONResult delete(@RequestParam String userId,@RequestParam String addressId) {if (StringUtils.isBlank(userId) || StringUtils.isBlank(addressId)) {return JSONResult.errorMsg("");}addressService.deleteUserAddress(userId, addressId);return JSONResult.ok();}
service
@Transactional(propagation = Propagation.REQUIRED)@Overridepublic void deleteUserAddress(String userId, String addressId) {UserAddress address = new UserAddress();address.setId(addressId);address.setUserId(userId);userAddressMapper.delete(address);}
15.6 修改收货地址
controller
@ApiOperation(value = "用户修改地址", notes = "用户修改地址", httpMethod = "POST")@PostMapping("/update")public JSONResult update(@RequestBody AddressBO addressBO) {if (StringUtils.isBlank(addressBO.getAddressId())) {return JSONResult.errorMsg("修改地址错误:addressId不能为空");}JSONResult checkRes = checkAddress(addressBO);if (checkRes.getStatus() != 200) {return checkRes;}addressService.updateUserAddress(addressBO);return JSONResult.ok();}
service
@Transactional(propagation = Propagation.REQUIRED)@Overridepublic void updateUserAddress(AddressBO addressBO) {String addressId = addressBO.getAddressId();UserAddress pendingAddress = new UserAddress();BeanUtils.copyProperties(addressBO, pendingAddress);pendingAddress.setId(addressId);pendingAddress.setUpdatedTime(new Date());userAddressMapper.updateByPrimaryKeySelective(pendingAddress);}
15.7 设置默认地址
controller
@ApiOperation(value = "用户设置默认地址", notes = "用户设置默认地址", httpMethod = "POST")@PostMapping("/setDefalut")public JSONResult setDefalut(@RequestParam String userId,@RequestParam String addressId) {if (StringUtils.isBlank(userId) || StringUtils.isBlank(addressId)) {return JSONResult.errorMsg("");}addressService.updateUserAddressToBeDefault(userId, addressId);return JSONResult.ok();}
servcie
@Transactional(propagation = Propagation.REQUIRED)@Overridepublic void updateUserAddressToBeDefault(String userId, String addressId) {// 1. 查找默认地址,设置为不默认UserAddress queryAddress = new UserAddress();queryAddress.setUserId(userId);queryAddress.setIsDefault(YesOrNo.YES.type);List<UserAddress> list = userAddressMapper.select(queryAddress);for (UserAddress ua : list) {ua.setIsDefault(YesOrNo.NO.type);userAddressMapper.updateByPrimaryKeySelective(ua);}// 2. 根据地址id修改为默认的地址UserAddress defaultAddress = new UserAddress();defaultAddress.setId(addressId);defaultAddress.setUserId(userId);defaultAddress.setIsDefault(YesOrNo.YES.type);userAddressMapper.updateByPrimaryKeySelective(defaultAddress);}
16. 订单流程和状态
16.1 需求分析

- 用户浏览商品
- 添加到购物车
- 订单结算:相当于是订单确认
- 发起支付

- 前往支付:这里生成订单?
- 待发货 变成 待收货,这里需要有 客服系统或则商家系统 来做这件事,本阶段不涉及该系统,可以直接修改状态来演示

- 付款中:有可能是一些第三方平台,或则政府平台,同步等待结果的话,可能会超过 10 秒,长的可能几分钟,这里异步等待回调
-
16.2 表设计

订单表: 收货 xx 快照:基本上下达填写收货地址后,再去更改收货地址信息,订单中的收货地址是不会被影响的
- 实际支付总价格:可能会有优惠卷之类的;但是在本阶段没有这些优惠功能
- 邮费:本阶段邮费都是包邮的
- 买家是否评价:判断是否对该订单是否进行了评价
- 逻辑删除状态:订单这种数据对我们公司来说是宝贵的数据,对于用户来说是看不到了,但是对于我们公司来说它是有价值的
订单商品关联表:子订单表
- 这里关于商品的一些信息,也是快照信息,而不是直接是一个 ID
- 商品 ID、规格 ID:这些数据也是比较珍贵的,可以用来做一些大数据分析之类的
订单状态表:可以和订单表进行合并的,但是为了性能,就拆开来了(列多,查询性能低)
- 订单 ID:与订单表的 ID 是一样的
- 订单状态:订单状态表;订单的每个状态更改都需要进行记录
10:待付款 20:已付款,待发货 30:已发货,待收货(7天自动确认) 40:交易成功(此时可以评价)50:交易关闭(待付款时,用户取消 或 长时间未付款,系统识别后自动关闭)
退货/退货(退款),此分支流程不做,所以不加入
16.4 聚合支付中心
16.5 提交并接受订单信息
前端代码
// 提交订单,创建订单submitOrder() {// 判断提交的商品不能为空var orderItemList = this.orderItemList;if (orderItemList == null || orderItemList == undefined || orderItemList == '' || orderItemList.length <= 0) {alert("没有商品信息,订单无法提交~!");return;}// 拼接规格idsvar itemSpecIds = "";for (var i = 0 ; i < orderItemList.length ; i ++) {var tmpSpecId = orderItemList[i].specId;itemSpecIds += tmpSpecId;if (i < orderItemList.length-1) {itemSpecIds += ",";}}// 判断选中的地址id不能为空var choosedAddressId = this.choosedAddressId;if (choosedAddressId == null || choosedAddressId == undefined || choosedAddressId == '') {alert("请选择收货地址!");return;}// 判断支付方式不能为空var choosedPayMethod = parseInt(this.choosedPayMethod);if (choosedPayMethod != 1 && choosedPayMethod != 2) {alert("请选择支付方式!");return;}// var newWindow = window.open();// 买家备注可以为空var orderRemarker = this.orderRemarker;// console.log(orderRemarker);var userInfo = this.userInfo;var serverUrl = app.serverUrl;axios.defaults.withCredentials = true;axios.post(serverUrl + '/orders/create',{"userId": userInfo.id,"itemSpecIds": itemSpecIds,"addressId": choosedAddressId,"payMethod": choosedPayMethod,"leftMsg": orderRemarker,},{headers: {'headerUserId': userInfo.id,'headerUserToken': userInfo.userUniqueToken}}).then(res => {if (res.data.status == 200) {// alert("OK");var orderId = res.data.data;// 判断是否微信还是支付宝支付if (choosedPayMethod == 1) {// 微信支付则跳转到微信支付页面,并且获得支付二维码window.location.href = "wxpay.html?orderId=" + orderId;} else if (choosedPayMethod == 2) {this.orderId = orderId;// 支付宝支付直接跳转window.location.href = "alipay.html?orderId=" + orderId + "&amount="+this.totalAmount;window.open("alipayTempTransit.html?orderId=" + orderId);// const newWindow = window.open();// 弹出的新窗口进行支付// newWindow.location = "alipayTempTransit.html?orderId=" + orderId;// this.$nextTick(()=> {// // 当前页面跳转后会轮训支付结果// newWindow.location.href = "alipay.html?orderId=" + orderId;// })} else {alert("目前只支持微信或支付宝支付!");}} else {alert(res.data.msg);}});},
大致流程如下:
- 获取到商品的规格 ID
- 拿到选择的地址 ID
- 拿到支付的方式
- 创建订单
/orders/create - 订单创建成功之后,再处理支付相关的业务,后面再说
这里主要是需要先实现创建订单的功能
用户下单(创建订单)接口
BO
/*** 用于创建订单的 BO 对象*/public class SubmitOrderBO {private String userId;private String itemSpecIds;private String addressId;private Integer payMethod;private String leftMsg;
service
//@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")@Servicepublic class OrderServiceImpl implements OrderService {//@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")@Autowiredprivate OrdersMapper ordersMapper;@Autowiredprivate Sid sid;@Autowiredprivate AddressService addressService;@Autowiredprivate ItemService itemService;@Autowiredprivate OrderItemsMapper orderItemsMapper;@Autowiredprivate OrderStatusMapper orderStatusMapper;@Transactional(propagation = Propagation.REQUIRED)@Overridepublic OrderVO createOrder(SubmitOrderBO submitOrderBO) {String userId = submitOrderBO.getUserId();String addressId = submitOrderBO.getAddressId();String itemSpecIds = submitOrderBO.getItemSpecIds();Integer payMethod = submitOrderBO.getPayMethod();String leftMsg = submitOrderBO.getLeftMsg();// 包邮费用设置为0Integer postAmount = 0;// 使用 sid 生成String orderId = sid.nextShort();UserAddress address = addressService.queryUserAddres(userId, addressId);// 1. 新订单数据保存Orders newOrder = new Orders();newOrder.setId(orderId);newOrder.setUserId(userId);// 地址快照newOrder.setReceiverName(address.getReceiver());newOrder.setReceiverMobile(address.getMobile());newOrder.setReceiverAddress(address.getProvince() + " "+ address.getCity() + " "+ address.getDistrict() + " "+ address.getDetail());// 在保存子订单后,计算出总价和真实价格后再填充// newOrder.setTotalAmount();// newOrder.setRealPayAmount();// 设置运费,真实的业务中,会动态设置的,要获取到newOrder.setPostAmount(postAmount);newOrder.setPayMethod(payMethod);newOrder.setLeftMsg(leftMsg); // 买家留言newOrder.setIsComment(YesOrNo.NO.type);newOrder.setIsDelete(YesOrNo.NO.type);newOrder.setCreatedTime(new Date());newOrder.setUpdatedTime(new Date());// 2. 循环根据 itemSpecIds 保存订单商品信息表String itemSpecIdArr[] = itemSpecIds.split(",");Integer totalAmount = 0; // 商品原价累计Integer realPayAmount = 0; // 优惠后的实际支付价格累计for (String itemSpecId : itemSpecIdArr) {// TODO 整合 redis 后,商品购买的数量重新从 redis 的购物车中获取int buyCounts = 1;// 2.1 根据规格 id,查询规格的具体信息,主要获取价格ItemsSpec itemSpec = itemService.queryItemSpecById(itemSpecId);totalAmount += itemSpec.getPriceNormal() * buyCounts;realPayAmount += itemSpec.getPriceDiscount() * buyCounts;// 2.2 根据商品 id,获得商品信息以及商品主图String itemId = itemSpec.getItemId();Items item = itemService.queryItemById(itemId);String imgUrl = itemService.queryItemMainImgById(itemId);// 2.3 循环保存子订单数据到数据库String subOrderId = sid.nextShort();OrderItems subOrderItem = new OrderItems();subOrderItem.setId(subOrderId);subOrderItem.setOrderId(orderId);subOrderItem.setItemId(itemId);subOrderItem.setItemName(item.getItemName());subOrderItem.setItemImg(imgUrl);subOrderItem.setBuyCounts(buyCounts);subOrderItem.setItemSpecId(itemSpecId);subOrderItem.setItemSpecName(itemSpec.getName());subOrderItem.setPrice(itemSpec.getPriceDiscount());orderItemsMapper.insert(subOrderItem);// 2.4 在用户提交订单以后,规格表中需要扣除库存itemService.decreaseItemSpecStock(itemSpecId, buyCounts);}newOrder.setTotalAmount(totalAmount);newOrder.setRealPayAmount(realPayAmount);ordersMapper.insert(newOrder);// 3. 保存订单状态表OrderStatus waitPayOrderStatus = new OrderStatus();waitPayOrderStatus.setOrderId(orderId);waitPayOrderStatus.setOrderStatus(OrderStatusEnum.WAIT_PAY.type);waitPayOrderStatus.setCreatedTime(new Date());orderStatusMapper.insert(waitPayOrderStatus);// 4. 构建商户订单,用于传给支付中心MerchantOrdersVO merchantOrdersVO = new MerchantOrdersVO();merchantOrdersVO.setMerchantOrderId(orderId);merchantOrdersVO.setMerchantUserId(userId);merchantOrdersVO.setAmount(realPayAmount + postAmount);merchantOrdersVO.setPayMethod(payMethod);// 5. 构建自定义订单 voOrderVO orderVO = new OrderVO();orderVO.setOrderId(orderId);orderVO.setMerchantOrdersVO(merchantOrdersVO);return orderVO;}}



















