image.png
image.png
image.png

1. 用户注册

1.1 用户名登录注册流程

image.png

1.2 邮箱注册流程

image.png

1.3 手机号注册

image.png

2.检验用户名是否存在

image.png
image.png

  1. <!-- apache 工具类 -->
  2. <!-- 文件检测 -->
  3. <!-- https://mvnrepository.com/artifact/org.apache.tika/tika-core -->
  4. <dependency>
  5. <groupId>org.apache.tika</groupId>
  6. <artifactId>tika-core</artifactId>
  7. <version>2.2.1</version>
  8. </dependency>
  9. <!-- https://mvnrepository.com/artifact/commons-codec/commons-codec -->
  10. <dependency>
  11. <groupId>commons-codec</groupId>
  12. <artifactId>commons-codec</artifactId>
  13. <version>1.15</version>
  14. </dependency>
  15. <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
  16. <dependency>
  17. <groupId>org.apache.commons</groupId>
  18. <artifactId>commons-lang3</artifactId>
  19. <version>3.12.0</version>
  20. </dependency>
  21. <!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
  22. <dependency>
  23. <groupId>commons-io</groupId>
  24. <artifactId>commons-io</artifactId>
  25. <version>2.11.0</version>
  26. </dependency>

image.png
运行测试
image.png

3. 状态码修改

image.png
image.png

4. 使用自定义响应数据类型

https://www.yuque.com/meishimokeqide/myi2rx/gahnvo/edit

image.png

5. 接受前端注册消息

我们统一将前端传过来的信息定义为xxxBO类
image.png
image.png

image.png
加密,设置时间,全局唯一id
image.png
image.png
image.png
image.png
image.png
https://www.yuque.com/meishimokeqide/myi2rx/iugpp7

6. 实现登录功能

image.png
image.png

7. 页面显示信息,使用Cookie

image.png

7.1 CookieUtils

  1. package com.lv.utils;
  2. import org.slf4j.Logger;
  3. import org.slf4j.LoggerFactory;
  4. import javax.servlet.http.Cookie;
  5. import javax.servlet.http.HttpServletRequest;
  6. import javax.servlet.http.HttpServletResponse;
  7. import java.io.UnsupportedEncodingException;
  8. import java.net.URLDecoder;
  9. import java.net.URLEncoder;
  10. /**
  11. *
  12. * @Title: CookieUtils.java
  13. * @Package com.lv.utils
  14. * @Description: Cookie 工具类
  15. * Copyright: Copyright (c)
  16. * Company: www.lv.com
  17. *
  18. * @author imooc
  19. * @version V1.0
  20. */
  21. public final class
  22. {
  23. final static Logger logger = LoggerFactory.getLogger(CookieUtils.class);
  24. /**
  25. *
  26. * @Description: 得到Cookie的值, 不编码
  27. * @param request
  28. * @param cookieName
  29. * @return
  30. */
  31. public static String getCookieValue(HttpServletRequest request, String cookieName) {
  32. return getCookieValue(request, cookieName, false);
  33. }
  34. /**
  35. *
  36. * @Description: 得到Cookie的值
  37. * @param request
  38. * @param cookieName
  39. * @param isDecoder
  40. * @return
  41. */
  42. public static String getCookieValue(HttpServletRequest request, String cookieName, boolean isDecoder) {
  43. Cookie[] cookieList = request.getCookies();
  44. if (cookieList == null || cookieName == null) {
  45. return null;
  46. }
  47. String retValue = null;
  48. try {
  49. for (int i = 0; i < cookieList.length; i++) {
  50. if (cookieList[i].getName().equals(cookieName)) {
  51. if (isDecoder) {
  52. retValue = URLDecoder.decode(cookieList[i].getValue(), "UTF-8");
  53. } else {
  54. retValue = cookieList[i].getValue();
  55. }
  56. break;
  57. }
  58. }
  59. } catch (UnsupportedEncodingException e) {
  60. e.printStackTrace();
  61. }
  62. return retValue;
  63. }
  64. /**
  65. *
  66. * @Description: 得到Cookie的值
  67. * @param request
  68. * @param cookieName
  69. * @param encodeString
  70. * @return
  71. */
  72. public static String getCookieValue(HttpServletRequest request, String cookieName, String encodeString) {
  73. Cookie[] cookieList = request.getCookies();
  74. if (cookieList == null || cookieName == null) {
  75. return null;
  76. }
  77. String retValue = null;
  78. try {
  79. for (int i = 0; i < cookieList.length; i++) {
  80. if (cookieList[i].getName().equals(cookieName)) {
  81. retValue = URLDecoder.decode(cookieList[i].getValue(), encodeString);
  82. break;
  83. }
  84. }
  85. } catch (UnsupportedEncodingException e) {
  86. e.printStackTrace();
  87. }
  88. return retValue;
  89. }
  90. /**
  91. *
  92. * @Description: 设置Cookie的值 不设置生效时间默认浏览器关闭即失效,也不编码
  93. * @param request
  94. * @param response
  95. * @param cookieName
  96. * @param cookieValue
  97. */
  98. public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
  99. String cookieValue) {
  100. setCookie(request, response, cookieName, cookieValue, -1);
  101. }
  102. /**
  103. *
  104. * @Description: 设置Cookie的值 在指定时间内生效,但不编码
  105. * @param request
  106. * @param response
  107. * @param cookieName
  108. * @param cookieValue
  109. * @param cookieMaxage
  110. */
  111. public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
  112. String cookieValue, int cookieMaxage) {
  113. setCookie(request, response, cookieName, cookieValue, cookieMaxage, false);
  114. }
  115. /**
  116. *
  117. * @Description: 设置Cookie的值 不设置生效时间,但编码
  118. * 在服务器被创建,返回给客户端,并且保存客户端
  119. * 如果设置了SETMAXAGE(int seconds),会把cookie保存在客户端的硬盘中
  120. * 如果没有设置,会默认把cookie保存在浏览器的内存中
  121. * 一旦设置setPath():只能通过设置的路径才能获取到当前的cookie信息
  122. * @param request
  123. * @param response
  124. * @param cookieName
  125. * @param cookieValue
  126. * @param isEncode
  127. */
  128. public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
  129. String cookieValue, boolean isEncode) {
  130. setCookie(request, response, cookieName, cookieValue, -1, isEncode);
  131. }
  132. /**
  133. *
  134. * @Description: 设置Cookie的值 在指定时间内生效, 编码参数
  135. * @param request
  136. * @param response
  137. * @param cookieName
  138. * @param cookieValue
  139. * @param cookieMaxage
  140. * @param isEncode
  141. */
  142. public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
  143. String cookieValue, int cookieMaxage, boolean isEncode) {
  144. doSetCookie(request, response, cookieName, cookieValue, cookieMaxage, isEncode);
  145. }
  146. /**
  147. *
  148. * @Description: 设置Cookie的值 在指定时间内生效, 编码参数(指定编码)
  149. * @param request
  150. * @param response
  151. * @param cookieName
  152. * @param cookieValue
  153. * @param cookieMaxage
  154. * @param encodeString
  155. */
  156. public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
  157. String cookieValue, int cookieMaxage, String encodeString) {
  158. doSetCookie(request, response, cookieName, cookieValue, cookieMaxage, encodeString);
  159. }
  160. /**
  161. *
  162. * @Description: 删除Cookie带cookie域名
  163. * @param request
  164. * @param response
  165. * @param cookieName
  166. */
  167. public static void deleteCookie(HttpServletRequest request, HttpServletResponse response,
  168. String cookieName) {
  169. doSetCookie(request, response, cookieName, null, -1, false);
  170. // doSetCookie(request, response, cookieName, "", -1, false);
  171. }
  172. /**
  173. *
  174. * @Description: 设置Cookie的值,并使其在指定时间内生效
  175. * @param request
  176. * @param response
  177. * @param cookieName
  178. * @param cookieValue
  179. * @param cookieMaxage cookie生效的最大秒数
  180. * @param isEncode
  181. */
  182. private static final void doSetCookie(HttpServletRequest request, HttpServletResponse response,
  183. String cookieName, String cookieValue, int cookieMaxage, boolean isEncode) {
  184. try {
  185. if (cookieValue == null) {
  186. cookieValue = "";
  187. } else if (isEncode) {
  188. cookieValue = URLEncoder.encode(cookieValue, "utf-8");
  189. }
  190. Cookie cookie = new Cookie(cookieName, cookieValue);
  191. if (cookieMaxage > 0)
  192. cookie.setMaxAge(cookieMaxage);
  193. if (null != request) {// 设置域名的cookie
  194. String domainName = getDomainName(request);
  195. logger.info("========== domainName: {} ==========", domainName);
  196. if (!"localhost".equals(domainName)) {
  197. cookie.setDomain(domainName);
  198. }
  199. }
  200. cookie.setPath("/");
  201. response.addCookie(cookie);
  202. } catch (Exception e) {
  203. e.printStackTrace();
  204. }
  205. }
  206. /**
  207. *
  208. * @Description: 设置Cookie的值,并使其在指定时间内生效
  209. * @param request
  210. * @param response
  211. * @param cookieName
  212. * @param cookieValue
  213. * @param cookieMaxage cookie生效的最大秒数
  214. * @param encodeString
  215. */
  216. private static final void doSetCookie(HttpServletRequest request, HttpServletResponse response,
  217. String cookieName, String cookieValue, int cookieMaxage, String encodeString) {
  218. try {
  219. if (cookieValue == null) {
  220. cookieValue = "";
  221. } else {
  222. cookieValue = URLEncoder.encode(cookieValue, encodeString);
  223. }
  224. Cookie cookie = new Cookie(cookieName, cookieValue);
  225. if (cookieMaxage > 0)
  226. cookie.setMaxAge(cookieMaxage);
  227. if (null != request) {// 设置域名的cookie
  228. String domainName = getDomainName(request);
  229. logger.info("========== domainName: {} ==========", domainName);
  230. if (!"localhost".equals(domainName)) {
  231. cookie.setDomain(domainName);
  232. }
  233. }
  234. cookie.setPath("/");
  235. response.addCookie(cookie);
  236. } catch (Exception e) {
  237. e.printStackTrace();
  238. }
  239. }
  240. /**
  241. *
  242. * @Description: 得到cookie的域名
  243. * @return
  244. */
  245. private static final String getDomainName(HttpServletRequest request) {
  246. String domainName = null;
  247. String serverName = request.getRequestURL().toString();
  248. if (serverName == null || serverName.equals("")) {
  249. domainName = "";
  250. } else {
  251. serverName = serverName.toLowerCase();
  252. serverName = serverName.substring(7);
  253. final int end = serverName.indexOf("/");
  254. serverName = serverName.substring(0, end);
  255. if (serverName.indexOf(":") > 0) {
  256. String[] ary = serverName.split("\\:");
  257. serverName = ary[0];
  258. }
  259. final String[] domains = serverName.split("\\.");
  260. int len = domains.length;
  261. if (len > 3 && !isIp(serverName)) {
  262. // www.xxx.com.cn
  263. domainName = "." + domains[len - 3] + "." + domains[len - 2] + "." + domains[len - 1];
  264. } else if (len <= 3 && len > 1) {
  265. // xxx.com or xxx.cn
  266. domainName = "." + domains[len - 2] + "." + domains[len - 1];
  267. } else {
  268. domainName = serverName;
  269. }
  270. }
  271. return domainName;
  272. }
  273. public static String trimSpaces(String IP){//去掉IP字符串前后所有的空格
  274. while(IP.startsWith(" ")){
  275. IP= IP.substring(1,IP.length()).trim();
  276. }
  277. while(IP.endsWith(" ")){
  278. IP= IP.substring(0,IP.length()-1).trim();
  279. }
  280. return IP;
  281. }
  282. public static boolean isIp(String IP){//判断是否是一个IP
  283. boolean b = false;
  284. IP = trimSpaces(IP);
  285. if(IP.matches("\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}")){
  286. String s[] = IP.split("\\.");
  287. if(Integer.parseInt(s[0])<255)
  288. if(Integer.parseInt(s[1])<255)
  289. if(Integer.parseInt(s[2])<255)
  290. if(Integer.parseInt(s[3])<255)
  291. b = true;
  292. }
  293. return b;
  294. }
  295. }

7.2 JsonUtils

  1. package com.lv.utils;
  2. import com.fasterxml.jackson.core.JsonProcessingException;
  3. import com.fasterxml.jackson.databind.JavaType;
  4. import com.fasterxml.jackson.databind.ObjectMapper;
  5. import java.util.List;
  6. /**
  7. *
  8. * @Title: JsonUtils.java
  9. * @Package com.lv.utils
  10. * @Description: json转换类
  11. * Copyright: Copyright (c)
  12. * Company: www.lv.com
  13. *
  14. * @author imooc
  15. */
  16. public class JsonUtils {
  17. // 定义jackson对象
  18. private static final ObjectMapper MAPPER = new ObjectMapper();
  19. /**
  20. * 将对象转换成json字符串。
  21. * @param data
  22. * @return
  23. */
  24. public static String objectToJson(Object data) {
  25. try {
  26. return MAPPER.writeValueAsString(data);
  27. } catch (JsonProcessingException e) {
  28. e.printStackTrace();
  29. }
  30. return null;
  31. }
  32. /**
  33. * 将json结果集转化为对象
  34. *
  35. * @param jsonData json数据
  36. * @param beanType 对象中的object类型
  37. * @return
  38. */
  39. public static <T> T jsonToPojo(String jsonData, Class<T> beanType) {
  40. try {
  41. T t = MAPPER.readValue(jsonData, beanType);
  42. return t;
  43. } catch (Exception e) {
  44. e.printStackTrace();
  45. }
  46. return null;
  47. }
  48. /**
  49. * 将json数据转换成pojo对象list
  50. * @param jsonData
  51. * @param beanType
  52. * @return
  53. */
  54. public static <T>List<T> jsonToList(String jsonData, Class<T> beanType) {
  55. JavaType javaType = MAPPER.getTypeFactory().constructParametricType(List.class, beanType);
  56. try {
  57. List<T> list = MAPPER.readValue(jsonData, javaType);
  58. return list;
  59. } catch (Exception e) {
  60. e.printStackTrace();
  61. }
  62. return null;
  63. }
  64. }

image.png
前后端对应
image.png
在注册后也将信息保存到cookie
image.png

8 用户退出,清除信息

image.png

9. 前端轮播图

创建servicel和serviceImpl
image.png

对固定的数据创建枚举类

image.png
创建controller
image.png

10. 首页分类需求分析

10.1 查询一级分类

image.png
image.png
image.png
image.png
image.png

10.2通过自连接查询二级分类

  1. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23122776/1641562629652-b4284cd6-99ba-452b-968f-428fe7296acf.png#crop=0&crop=0&crop=1&crop=1&height=250&id=u09f58102&margin=%5Bobject%20Object%5D&name=image.png&originHeight=500&originWidth=512&originalType=binary&ratio=1&rotation=0&showTitle=false&size=123094&status=done&style=none&title=&width=256)

image.png

10.3 通过自定义Mapper实现查询子分类

1.自定义mapper类
我们这里需要多表联查所以单体查询已经不能满足需求,要创建自定义mapper
image.png

  1. package com.lv.mapper;
  2. import com.lv.pojo.vo.CategoryVO;
  3. import com.lv.pojo.vo.NewItemsVO;
  4. import org.apache.ibatis.annotations.Mapper;
  5. import org.apache.ibatis.annotations.Param;
  6. import java.util.List;
  7. import java.util.Map;
  8. /**
  9. * @author ziming.xing
  10. * Create Date:2021/5/18
  11. */
  12. @Mapper
  13. public interface CategoryCustomMapper {
  14. List<CategoryVO> getSubCatList(@Param("rootCatId") Integer rootCatId);
  15. List<NewItemsVO> getSixNewItemLazy(@Param("paramsMap") Map<String, Object> paramsMap);
  16. }

2.创建vo包,之前的bo是前端传给后端的数据,这个vo是后端传给前端,前端view上显示的数据
image.png
image.png
image.png

3.创建mybatis的xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  3. <mapper namespace="com.lv.mapper.CategoryCustomMapper">
  4. <!--
  5. collection 标签:用于定义关联的list集合类型的封装规则
  6. property:对应三级分类的list属性名
  7. ofType:集合的类型,三级分类的VO
  8. -->
  9. <resultMap id="MyCategoryVO" type="com.lv.pojo.vo.CategoryVO">
  10. <id column="id" jdbcType="INTEGER" property="id"/>
  11. <result column="name" jdbcType="VARCHAR" property="name"/>
  12. <result column="type" jdbcType="INTEGER" property="type"/>
  13. <result column="father_id" jdbcType="INTEGER" property="fatherId"/>
  14. <collection property="subCatList" ofType="com.lv.pojo.vo.SubCategoryVO">
  15. <id column="sub_id" jdbcType="INTEGER" property="subId"/>
  16. <result column="sub_name" jdbcType="VARCHAR" property="subName"/>
  17. <result column="sub_type" jdbcType="INTEGER" property="subType"/>
  18. <result column="sub_father_id" jdbcType="INTEGER" property="subFatherId"/>
  19. </collection>
  20. </resultMap>
  21. <select id="getSubCatList" resultMap="MyCategoryVO">
  22. SELECT f.id,
  23. f.`name`,
  24. f.type,
  25. f.father_id,
  26. c.id AS sub_id,
  27. c.`name` AS sub_name,
  28. c.type AS sub_type,
  29. c.father_id AS sub_father_id
  30. FROM `category` AS f
  31. LEFT JOIN `category` AS c ON f.id = c.father_id
  32. WHERE f.father_id = #{rootCatId,jdbcType=INTEGER}
  33. </select>
  34. </mapper>

image.png
image.pngimage.png

10.4.首页商品推荐

image.png

  1. <resultMap id="MyNewItemsVO" type="com.lv.pojo.vo.NewItemsVO">
  2. <id column="root_cat_id" jdbcType="INTEGER" property="rootCatId"/>
  3. <result column="root_cat_name" jdbcType="VARCHAR" property="rootCatName"/>
  4. <result column="slogan" jdbcType="VARCHAR" property="slogan"/>
  5. <result column="cat_image" jdbcType="VARCHAR" property="catImage"/>
  6. <result column="bg_color" jdbcType="VARCHAR" property="bgColor"/>
  7. <collection property="simpleItemList" ofType="cn.myst.web.pojo.vo.SimpleItemVO">
  8. <result column="item_id" jdbcType="VARCHAR" property="itemId"/>
  9. <result column="item_name" jdbcType="VARCHAR" property="itemName"/>
  10. <result column="item_url" jdbcType="VARCHAR" property="itemUrl"/>
  11. </collection>
  12. </resultMap>
  13. <select id="getSixNewItemLazy" resultMap="MyNewItemsVO">
  14. SELECT f.id AS root_cat_id,
  15. f.`name` AS root_cat_name,
  16. f.slogan,
  17. f.cat_image,
  18. f.bg_color,
  19. i.id AS item_id,
  20. i.item_name AS item_name,
  21. ii.url AS item_url,
  22. i.created_time
  23. FROM category f
  24. LEFT JOIN items i ON f.id = i.root_cat_id
  25. LEFT JOIN items_img ii ON i.id = ii.item_id
  26. WHERE f.type = 1
  27. AND i.root_cat_id = #{paramsMap.rootCatId}
  28. AND ii.is_main = 1
  29. ORDER BY i.created_time DESC
  30. LIMIT 0,6
  31. </select>

mapper
image.png
serviceImpl
image.png
controller
image.png

11.商品详情功能分析

11.1 搜索信息

image.png
image.png
image.png

11.2 编写相关查询servcie

我们查询都是和商品相关的,并且都是单表查询,所以我们使用通用mapper就可以
image.png
image.png
创建一个vo将数据传给前端

image.png
image.png
image.png

12.商品的评价

12.1 查询商品评价等级数量

创建评价vo
image.png
创建评价枚举
image.png
service层,查询评价等级和各个等级数量,在做和
image.png
controller层
image.png

image.pngimage.png

12.2 自定义mapper查询

因为涉及到多表关联,通用mapper不能完成

  • 数据库设计

image.png

  • mapper层

image.pngimage.png

  • service层

image.png

12.3 mybatis分页插件

引入相关依赖,设置yml
image.png
image.png
image.png

  • controller层

image.png
创建一个BaseController
image.png
image.png

12.4 信息脱敏

image.png

  1. /**
  2. * 通用脱敏工具类
  3. * 可用于:
  4. * 用户名
  5. * 手机号
  6. * 邮箱
  7. * 地址等
  8. */
  9. public class DesensitizationUtil {
  10. private static final int SIZE = 6;
  11. private static final String SYMBOL = "*";
  12. /**
  13. * 通用脱敏方法
  14. * @param value
  15. * @return
  16. */
  17. public static String commonDisplay(String value) {
  18. if (null == value || "".equals(value)) {
  19. return value;
  20. }
  21. int len = value.length();
  22. int pamaone = len / 2;
  23. int pamatwo = pamaone - 1;
  24. int pamathree = len % 2;
  25. StringBuilder stringBuilder = new StringBuilder();
  26. if (len <= 2) {
  27. if (pamathree == 1) {
  28. return SYMBOL;
  29. }
  30. stringBuilder.append(SYMBOL);
  31. stringBuilder.append(value.charAt(len - 1));
  32. } else {
  33. if (pamatwo <= 0) {
  34. stringBuilder.append(value.substring(0, 1));
  35. stringBuilder.append(SYMBOL);
  36. stringBuilder.append(value.substring(len - 1, len));
  37. } else if (pamatwo >= SIZE / 2 && SIZE + 1 != len) {
  38. int pamafive = (len - SIZE) / 2;
  39. stringBuilder.append(value.substring(0, pamafive));
  40. for (int i = 0; i < SIZE; i++) {
  41. stringBuilder.append(SYMBOL);
  42. }
  43. if ((pamathree == 0 && SIZE / 2 == 0) || (pamathree != 0 && SIZE % 2 != 0)) {
  44. stringBuilder.append(value.substring(len - pamafive, len));
  45. } else {
  46. stringBuilder.append(value.substring(len - (pamafive + 1), len));
  47. }
  48. } else {
  49. int pamafour = len - 2;
  50. stringBuilder.append(value.substring(0, 1));
  51. for (int i = 0; i < pamafour; i++) {
  52. stringBuilder.append(SYMBOL);
  53. }
  54. stringBuilder.append(value.substring(len - 1, len));
  55. }
  56. }
  57. return stringBuilder.toString();
  58. }
  59. }

image.png

13. 商品搜索功能

13.1主页搜索框关键字商品搜索

  • 会用到多表关联,也会用到分页操作

image.png

  • 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 (

    1. SELECT
    2. item_id,MIN(price_discount) as price_discount
    3. FROM
    4. items_spec
    5. GROUP BY
    6. item_id

    ) tempSpec ON i.id =tempSpec.item_id WHERE ii.is_main =1;

  1. xml
  2. ```xml
  3. <!-- k:默认,代表默认排序,根据name-->
  4. <!-- c:根据销量排序-->
  5. <!-- p:根据价格排序-->
  6. <!-- -->
  7. <select id="searchItems" parameterType="Map" resultType="com.lv.pojo.vo.SearchItemsVO">
  8. SELECT
  9. i.id as itemId,
  10. i.item_name as itemName,
  11. i.sell_counts as sellCounts,
  12. ii.url as imgUrl,
  13. tempSpec.price_discount as price
  14. FROM
  15. items i
  16. LEFT JOIN
  17. items_img ii
  18. ON
  19. i.id=ii.item_id
  20. LEFT JOIN
  21. ( SELECT item_id,MIN(price_discount) as price_discount FROM items_spec GROUP BY item_id ) tempSpec
  22. ON
  23. i.id =tempSpec.item_id
  24. WHERE
  25. ii.is_main =1;
  26. <if test="paramsMap.keywords !=null and paramsMap.keywords !='' ">
  27. AND i.item_name LIKE '%${paramsMap.keywords}%'
  28. </if>
  29. ORDER BY
  30. <choose>
  31. <when test="paramsMap.sort==&quot;c&quot;">
  32. i.sell_counts desc
  33. </when>
  34. <when test="paramsMap.sort==&quot;p&quot;">
  35. tempSpec.price_discount asc
  36. </when>
  37. <otherwise>
  38. i.item_name asc
  39. </otherwise>
  40. </choose>
  41. </select>

多表联查,要用到自定义mapper和VO
image.png
mapper
image.png
service
image.png
controller
image.png

13.2 前端业务与商品分类搜索

通过主页的三级分类id,进行搜索
image.png

  1. <select id="searchItemsByThirdCat" parameterType="Map" resultType="com.lv.pojo.vo.SearchItemsVO">
  2. SELECT
  3. i.id as itemId,
  4. i.item_name as itemName,
  5. i.sell_counts as sellCounts,
  6. ii.url as imgUrl,
  7. tempSpec.price_discount as price
  8. FROM
  9. items i
  10. LEFT JOIN
  11. items_img ii
  12. ON
  13. i.id=ii.item_id
  14. LEFT JOIN
  15. ( SELECT item_id,MIN(price_discount) as price_discount FROM items_spec GROUP BY item_id ) tempSpec
  16. ON
  17. i.id =tempSpec.item_id
  18. WHERE
  19. ii.is_main =1
  20. AND
  21. i.cat_id = #{paramsMap.catId}
  22. ORDER BY
  23. <choose>
  24. <when test="paramsMap.sort==&quot;c&quot;">
  25. i.sell_counts desc
  26. </when>
  27. <when test="paramsMap.sort==&quot;p&quot;">
  28. tempSpec.price_discount asc
  29. </when>
  30. <otherwise>
  31. i.item_name asc
  32. </otherwise>
  33. </choose>
  34. </select>

mapper
image.png
service
image.png
controller
image.png

14.购物车

14.1 购物车存储形式

使用Cookie和redis一起做,同时我们可以仿造京东购物车
image.png
image.png
image.png
image.png

14.2 使用Cookie和Redis,编写未登录和已登录加入购物车的代码

image.png
前端需要把购物车信息传给后端,这里需要前端传一个VO给后端
image.png

14.3 渲染(刷新)购物车

不能直接使用Cookie中的信息,因为有可能商品的信息,价格发生了更改,我们需要通过商品规格id重新刷新一下购物车信息。

  • 要使用到多表关联,我们要用自定义mapper和数据层的VO,封装购物车信息

image.png

  • mapper.xml ```xml

  1. - **自定义mapper**
  2. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23122776/1641818351909-4a2b5115-f56a-481a-a35a-30d135f2e809.png#crop=0&crop=0&crop=1&crop=1&height=339&id=uc306c198&margin=%5Bobject%20Object%5D&name=image.png&originHeight=677&originWidth=1622&originalType=binary&ratio=1&rotation=0&showTitle=false&size=142066&status=done&style=none&title=&width=811)
  3. - **serviceserviceImpl**
  4. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23122776/1641818513489-42d26bc4-0906-445d-afcf-b7ca65464c4d.png#crop=0&crop=0&crop=1&crop=1&height=78&id=u32f05707&margin=%5Bobject%20Object%5D&name=image.png&originHeight=156&originWidth=711&originalType=binary&ratio=1&rotation=0&showTitle=false&size=20808&status=done&style=none&title=&width=355.5)<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/23122776/1641818729805-ce733816-0c06-44a5-806a-97a42e122164.png#crop=0&crop=0&crop=1&crop=1&height=170&id=u679d5e47&margin=%5Bobject%20Object%5D&name=image.png&originHeight=339&originWidth=810&originalType=binary&ratio=1&rotation=0&showTitle=false&size=50269&status=done&style=none&title=&width=405)
  5. - **controller**
  6. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23122776/1641819250247-36d03f1d-b7c1-45cd-9baa-0b1ecda63760.png#crop=0&crop=0&crop=1&crop=1&height=313&id=ub6d15422&margin=%5Bobject%20Object%5D&name=image.png&originHeight=626&originWidth=1756&originalType=binary&ratio=1&rotation=0&showTitle=false&size=156856&status=done&style=none&title=&width=878)
  7. <a name="fpFQe"></a>
  8. ## 14.4 其他业务操作
  9. 删除操作
  10. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23122776/1641868535792-9c9096e9-4cf0-4df3-b3eb-72676fdb9019.png#crop=0&crop=0&crop=1&crop=1&height=261&id=u637dee5b&margin=%5Bobject%20Object%5D&name=image.png&originHeight=522&originWidth=1047&originalType=binary&ratio=1&rotation=0&showTitle=false&size=88953&status=done&style=none&title=&width=523.5)
  11. <a name="wKXlm"></a>
  12. # 15.收货地址
  13. <a name="pDcFe"></a>
  14. ## 15.1 需求分析
  15. - 进入页面后,需要将所有的收货地址列出来
  16. - 可以新增地址
  17. - 可以修改之前的地址
  18. - 可以设置为默认:默认会被选中
  19. <a name="G6QNI"></a>
  20. ## 15.2 表设计
  21. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23122776/1641877492404-44980249-5741-4e70-be8e-620a639c3e6e.png#crop=0&crop=0&crop=1&crop=1&height=269&id=u17865d1f&margin=%5Bobject%20Object%5D&name=image.png&originHeight=538&originWidth=577&originalType=binary&ratio=1&rotation=0&showTitle=false&size=206971&status=done&style=none&title=&width=288.5)
  22. - 关联用户 ID:一个用户会有多个收货地址
  23. - 扩展字段:可能会有详细地址 2
  24. 用户在确认订单页面,可以针对收货地址做如下操作:
  25. 1. 查询用户的所有收货地址列表
  26. 2. 新增收货地址
  27. 3. 删除收货地址
  28. 4. 修改收货地址
  29. 5. 设置默认地址
  30. 另外在编辑地址时,所需要的省市区信息的维护:本课程是放在前端 js 中维护的,这种信息一般半年才维护一次,变动不太大。
  31. <a name="qv42K"></a>
  32. ## 15.3 查询用户的所有收货地址列表
  33. controller
  34. ```java
  35. @ApiOperation(value = "根据用户id查询收货地址列表", notes = "根据用户id查询收货地址列表", httpMethod = "POST")
  36. @PostMapping("/list")
  37. public JSONResult list(
  38. @RequestParam String userId) {
  39. if (StringUtils.isBlank(userId)) {
  40. return JSONResult.errorMsg("");
  41. }
  42. List<UserAddress> list = addressService.queryAll(userId);
  43. return JSONResult.ok(list);
  44. }

service

  1. @Transactional(propagation = Propagation.SUPPORTS)
  2. @Override
  3. public List<UserAddress> queryAll(String userId) {
  4. UserAddress ua = new UserAddress();
  5. ua.setUserId(userId);
  6. return userAddressMapper.select(ua);
  7. }

15.4 新增收获地址

  1. @ApiOperation(value = "用户新增地址", notes = "用户新增地址", httpMethod = "POST")
  2. @PostMapping("/add")
  3. public JSONResult add(@RequestBody AddressBO addressBO) {
  4. JSONResult checkRes = checkAddress(addressBO);
  5. if (checkRes.getStatus() != 200) {
  6. return checkRes;
  7. }
  8. addressService.addNewUserAddress(addressBO);
  9. return JSONResult.ok();
  10. }
  11. private JSONResult checkAddress(AddressBO addressBO) {
  12. String receiver = addressBO.getReceiver();
  13. if (StringUtils.isBlank(receiver)) {
  14. return JSONResult.errorMsg("收货人不能为空");
  15. }
  16. if (receiver.length() > 12) {
  17. return JSONResult.errorMsg("收货人姓名不能太长");
  18. }
  19. String mobile = addressBO.getMobile();
  20. if (StringUtils.isBlank(mobile)) {
  21. return JSONResult.errorMsg("收货人手机号不能为空");
  22. }
  23. if (mobile.length() != 11) {
  24. return JSONResult.errorMsg("收货人手机号长度不正确");
  25. }
  26. boolean isMobileOk = MobileEmailUtils.checkMobileIsOk(mobile);
  27. if (!isMobileOk) {
  28. return JSONResult.errorMsg("收货人手机号格式不正确");
  29. }
  30. String province = addressBO.getProvince();
  31. String city = addressBO.getCity();
  32. String district = addressBO.getDistrict();
  33. String detail = addressBO.getDetail();
  34. if (StringUtils.isBlank(province) ||
  35. StringUtils.isBlank(city) ||
  36. StringUtils.isBlank(district) ||
  37. StringUtils.isBlank(detail)) {
  38. return JSONResult.errorMsg("收货地址信息不能为空");
  39. }
  40. return JSONResult.ok();
  41. }

Bo

  1. /**
  2. * 用户新增或修改地址的 BO
  3. */
  4. public class AddressBO {
  5. private String addressId;
  6. private String userId;
  7. private String receiver;
  8. private String mobile;
  9. private String province;
  10. private String city;
  11. private String district;
  12. private String detail;

service

  1. @Transactional(propagation = Propagation.REQUIRED)
  2. @Override
  3. public void addNewUserAddress(AddressBO addressBO) {
  4. // 1. 判断当前用户是否存在地址,如果没有,则新增为‘默认地址’
  5. Integer isDefault = 0;
  6. List<UserAddress> addressList = this.queryAll(addressBO.getUserId());
  7. if (addressList == null || addressList.isEmpty() || addressList.size() == 0) {
  8. isDefault = 1;
  9. }
  10. String addressId = sid.nextShort();
  11. // 2. 保存地址到数据库
  12. UserAddress newAddress = new UserAddress();
  13. BeanUtils.copyProperties(addressBO, newAddress);
  14. newAddress.setId(addressId);
  15. newAddress.setIsDefault(isDefault);
  16. newAddress.setCreatedTime(new Date());
  17. newAddress.setUpdatedTime(new Date());
  18. userAddressMapper.insert(newAddress);
  19. }

15.5 删除收货地址

controller

  1. @ApiOperation(value = "用户删除地址", notes = "用户删除地址", httpMethod = "POST")
  2. @PostMapping("/delete")
  3. public JSONResult delete(
  4. @RequestParam String userId,
  5. @RequestParam String addressId) {
  6. if (StringUtils.isBlank(userId) || StringUtils.isBlank(addressId)) {
  7. return JSONResult.errorMsg("");
  8. }
  9. addressService.deleteUserAddress(userId, addressId);
  10. return JSONResult.ok();
  11. }

service

  1. @Transactional(propagation = Propagation.REQUIRED)
  2. @Override
  3. public void deleteUserAddress(String userId, String addressId) {
  4. UserAddress address = new UserAddress();
  5. address.setId(addressId);
  6. address.setUserId(userId);
  7. userAddressMapper.delete(address);
  8. }

15.6 修改收货地址

controller

  1. @ApiOperation(value = "用户修改地址", notes = "用户修改地址", httpMethod = "POST")
  2. @PostMapping("/update")
  3. public JSONResult update(@RequestBody AddressBO addressBO) {
  4. if (StringUtils.isBlank(addressBO.getAddressId())) {
  5. return JSONResult.errorMsg("修改地址错误:addressId不能为空");
  6. }
  7. JSONResult checkRes = checkAddress(addressBO);
  8. if (checkRes.getStatus() != 200) {
  9. return checkRes;
  10. }
  11. addressService.updateUserAddress(addressBO);
  12. return JSONResult.ok();
  13. }

service

  1. @Transactional(propagation = Propagation.REQUIRED)
  2. @Override
  3. public void updateUserAddress(AddressBO addressBO) {
  4. String addressId = addressBO.getAddressId();
  5. UserAddress pendingAddress = new UserAddress();
  6. BeanUtils.copyProperties(addressBO, pendingAddress);
  7. pendingAddress.setId(addressId);
  8. pendingAddress.setUpdatedTime(new Date());
  9. userAddressMapper.updateByPrimaryKeySelective(pendingAddress);
  10. }

15.7 设置默认地址

controller

  1. @ApiOperation(value = "用户设置默认地址", notes = "用户设置默认地址", httpMethod = "POST")
  2. @PostMapping("/setDefalut")
  3. public JSONResult setDefalut(
  4. @RequestParam String userId,
  5. @RequestParam String addressId) {
  6. if (StringUtils.isBlank(userId) || StringUtils.isBlank(addressId)) {
  7. return JSONResult.errorMsg("");
  8. }
  9. addressService.updateUserAddressToBeDefault(userId, addressId);
  10. return JSONResult.ok();
  11. }

servcie

  1. @Transactional(propagation = Propagation.REQUIRED)
  2. @Override
  3. public void updateUserAddressToBeDefault(String userId, String addressId) {
  4. // 1. 查找默认地址,设置为不默认
  5. UserAddress queryAddress = new UserAddress();
  6. queryAddress.setUserId(userId);
  7. queryAddress.setIsDefault(YesOrNo.YES.type);
  8. List<UserAddress> list = userAddressMapper.select(queryAddress);
  9. for (UserAddress ua : list) {
  10. ua.setIsDefault(YesOrNo.NO.type);
  11. userAddressMapper.updateByPrimaryKeySelective(ua);
  12. }
  13. // 2. 根据地址id修改为默认的地址
  14. UserAddress defaultAddress = new UserAddress();
  15. defaultAddress.setId(addressId);
  16. defaultAddress.setUserId(userId);
  17. defaultAddress.setIsDefault(YesOrNo.YES.type);
  18. userAddressMapper.updateByPrimaryKeySelective(defaultAddress);
  19. }

16. 订单流程和状态

16.1 需求分析

image.png

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

image.png

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

image.png

  • 付款中:有可能是一些第三方平台,或则政府平台,同步等待结果的话,可能会超过 10 秒,长的可能几分钟,这里异步等待回调
  • 取消订单,待退款:是需要客服等人员去审核的

    16.2 表设计

    image.png
    订单表:

  • 收货 xx 快照:基本上下达填写收货地址后,再去更改收货地址信息,订单中的收货地址是不会被影响的

  • 实际支付总价格:可能会有优惠卷之类的;但是在本阶段没有这些优惠功能
  • 邮费:本阶段邮费都是包邮的
  • 买家是否评价:判断是否对该订单是否进行了评价
  • 逻辑删除状态:订单这种数据对我们公司来说是宝贵的数据,对于用户来说是看不到了,但是对于我们公司来说它是有价值的

订单商品关联表:子订单表

  • 这里关于商品的一些信息,也是快照信息,而不是直接是一个 ID
  • 商品 ID、规格 ID:这些数据也是比较珍贵的,可以用来做一些大数据分析之类的

订单状态表:可以和订单表进行合并的,但是为了性能,就拆开来了(列多,查询性能低)

  • 订单 ID:与订单表的 ID 是一样的
  • 订单状态:订单状态表;订单的每个状态更改都需要进行记录
    10:待付款 20:已付款,待发货 30:已发货,待收货(7天自动确认) 40:交易成功(此时可以评价)50:交易关闭(待付款时,用户取消 或 长时间未付款,系统识别后自动关闭)
    退货/退货(退款),此分支流程不做,所以不加入

16.4 聚合支付中心

image.png

16.5 提交并接受订单信息

前端代码

  1. // 提交订单,创建订单
  2. submitOrder() {
  3. // 判断提交的商品不能为空
  4. var orderItemList = this.orderItemList;
  5. if (orderItemList == null || orderItemList == undefined || orderItemList == '' || orderItemList.length <= 0) {
  6. alert("没有商品信息,订单无法提交~!");
  7. return;
  8. }
  9. // 拼接规格ids
  10. var itemSpecIds = "";
  11. for (var i = 0 ; i < orderItemList.length ; i ++) {
  12. var tmpSpecId = orderItemList[i].specId;
  13. itemSpecIds += tmpSpecId;
  14. if (i < orderItemList.length-1) {
  15. itemSpecIds += ",";
  16. }
  17. }
  18. // 判断选中的地址id不能为空
  19. var choosedAddressId = this.choosedAddressId;
  20. if (choosedAddressId == null || choosedAddressId == undefined || choosedAddressId == '') {
  21. alert("请选择收货地址!");
  22. return;
  23. }
  24. // 判断支付方式不能为空
  25. var choosedPayMethod = parseInt(this.choosedPayMethod);
  26. if (choosedPayMethod != 1 && choosedPayMethod != 2) {
  27. alert("请选择支付方式!");
  28. return;
  29. }
  30. // var newWindow = window.open();
  31. // 买家备注可以为空
  32. var orderRemarker = this.orderRemarker;
  33. // console.log(orderRemarker);
  34. var userInfo = this.userInfo;
  35. var serverUrl = app.serverUrl;
  36. axios.defaults.withCredentials = true;
  37. axios.post(
  38. serverUrl + '/orders/create',
  39. {
  40. "userId": userInfo.id,
  41. "itemSpecIds": itemSpecIds,
  42. "addressId": choosedAddressId,
  43. "payMethod": choosedPayMethod,
  44. "leftMsg": orderRemarker,
  45. },
  46. {
  47. headers: {
  48. 'headerUserId': userInfo.id,
  49. 'headerUserToken': userInfo.userUniqueToken
  50. }
  51. })
  52. .then(res => {
  53. if (res.data.status == 200) {
  54. // alert("OK");
  55. var orderId = res.data.data;
  56. // 判断是否微信还是支付宝支付
  57. if (choosedPayMethod == 1) {
  58. // 微信支付则跳转到微信支付页面,并且获得支付二维码
  59. window.location.href = "wxpay.html?orderId=" + orderId;
  60. } else if (choosedPayMethod == 2) {
  61. this.orderId = orderId;
  62. // 支付宝支付直接跳转
  63. window.location.href = "alipay.html?orderId=" + orderId + "&amount="+this.totalAmount;
  64. window.open("alipayTempTransit.html?orderId=" + orderId);
  65. // const newWindow = window.open();
  66. // 弹出的新窗口进行支付
  67. // newWindow.location = "alipayTempTransit.html?orderId=" + orderId;
  68. // this.$nextTick(()=> {
  69. // // 当前页面跳转后会轮训支付结果
  70. // newWindow.location.href = "alipay.html?orderId=" + orderId;
  71. // })
  72. } else {
  73. alert("目前只支持微信或支付宝支付!");
  74. }
  75. } else {
  76. alert(res.data.msg);
  77. }
  78. });
  79. },

大致流程如下:

  1. 获取到商品的规格 ID
  2. 拿到选择的地址 ID
  3. 拿到支付的方式
  4. 创建订单 /orders/create
  5. 订单创建成功之后,再处理支付相关的业务,后面再说

这里主要是需要先实现创建订单的功能

用户下单(创建订单)接口

BO

  1. /**
  2. * 用于创建订单的 BO 对象
  3. */
  4. public class SubmitOrderBO {
  5. private String userId;
  6. private String itemSpecIds;
  7. private String addressId;
  8. private Integer payMethod;
  9. private String leftMsg;

service

  1. //@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
  2. @Service
  3. public class OrderServiceImpl implements OrderService {
  4. //@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
  5. @Autowired
  6. private OrdersMapper ordersMapper;
  7. @Autowired
  8. private Sid sid;
  9. @Autowired
  10. private AddressService addressService;
  11. @Autowired
  12. private ItemService itemService;
  13. @Autowired
  14. private OrderItemsMapper orderItemsMapper;
  15. @Autowired
  16. private OrderStatusMapper orderStatusMapper;
  17. @Transactional(propagation = Propagation.REQUIRED)
  18. @Override
  19. public OrderVO createOrder(SubmitOrderBO submitOrderBO) {
  20. String userId = submitOrderBO.getUserId();
  21. String addressId = submitOrderBO.getAddressId();
  22. String itemSpecIds = submitOrderBO.getItemSpecIds();
  23. Integer payMethod = submitOrderBO.getPayMethod();
  24. String leftMsg = submitOrderBO.getLeftMsg();
  25. // 包邮费用设置为0
  26. Integer postAmount = 0;
  27. // 使用 sid 生成
  28. String orderId = sid.nextShort();
  29. UserAddress address = addressService.queryUserAddres(userId, addressId);
  30. // 1. 新订单数据保存
  31. Orders newOrder = new Orders();
  32. newOrder.setId(orderId);
  33. newOrder.setUserId(userId);
  34. // 地址快照
  35. newOrder.setReceiverName(address.getReceiver());
  36. newOrder.setReceiverMobile(address.getMobile());
  37. newOrder.setReceiverAddress(address.getProvince() + " "
  38. + address.getCity() + " "
  39. + address.getDistrict() + " "
  40. + address.getDetail());
  41. // 在保存子订单后,计算出总价和真实价格后再填充
  42. // newOrder.setTotalAmount();
  43. // newOrder.setRealPayAmount();
  44. // 设置运费,真实的业务中,会动态设置的,要获取到
  45. newOrder.setPostAmount(postAmount);
  46. newOrder.setPayMethod(payMethod);
  47. newOrder.setLeftMsg(leftMsg); // 买家留言
  48. newOrder.setIsComment(YesOrNo.NO.type);
  49. newOrder.setIsDelete(YesOrNo.NO.type);
  50. newOrder.setCreatedTime(new Date());
  51. newOrder.setUpdatedTime(new Date());
  52. // 2. 循环根据 itemSpecIds 保存订单商品信息表
  53. String itemSpecIdArr[] = itemSpecIds.split(",");
  54. Integer totalAmount = 0; // 商品原价累计
  55. Integer realPayAmount = 0; // 优惠后的实际支付价格累计
  56. for (String itemSpecId : itemSpecIdArr) {
  57. // TODO 整合 redis 后,商品购买的数量重新从 redis 的购物车中获取
  58. int buyCounts = 1;
  59. // 2.1 根据规格 id,查询规格的具体信息,主要获取价格
  60. ItemsSpec itemSpec = itemService.queryItemSpecById(itemSpecId);
  61. totalAmount += itemSpec.getPriceNormal() * buyCounts;
  62. realPayAmount += itemSpec.getPriceDiscount() * buyCounts;
  63. // 2.2 根据商品 id,获得商品信息以及商品主图
  64. String itemId = itemSpec.getItemId();
  65. Items item = itemService.queryItemById(itemId);
  66. String imgUrl = itemService.queryItemMainImgById(itemId);
  67. // 2.3 循环保存子订单数据到数据库
  68. String subOrderId = sid.nextShort();
  69. OrderItems subOrderItem = new OrderItems();
  70. subOrderItem.setId(subOrderId);
  71. subOrderItem.setOrderId(orderId);
  72. subOrderItem.setItemId(itemId);
  73. subOrderItem.setItemName(item.getItemName());
  74. subOrderItem.setItemImg(imgUrl);
  75. subOrderItem.setBuyCounts(buyCounts);
  76. subOrderItem.setItemSpecId(itemSpecId);
  77. subOrderItem.setItemSpecName(itemSpec.getName());
  78. subOrderItem.setPrice(itemSpec.getPriceDiscount());
  79. orderItemsMapper.insert(subOrderItem);
  80. // 2.4 在用户提交订单以后,规格表中需要扣除库存
  81. itemService.decreaseItemSpecStock(itemSpecId, buyCounts);
  82. }
  83. newOrder.setTotalAmount(totalAmount);
  84. newOrder.setRealPayAmount(realPayAmount);
  85. ordersMapper.insert(newOrder);
  86. // 3. 保存订单状态表
  87. OrderStatus waitPayOrderStatus = new OrderStatus();
  88. waitPayOrderStatus.setOrderId(orderId);
  89. waitPayOrderStatus.setOrderStatus(OrderStatusEnum.WAIT_PAY.type);
  90. waitPayOrderStatus.setCreatedTime(new Date());
  91. orderStatusMapper.insert(waitPayOrderStatus);
  92. // 4. 构建商户订单,用于传给支付中心
  93. MerchantOrdersVO merchantOrdersVO = new MerchantOrdersVO();
  94. merchantOrdersVO.setMerchantOrderId(orderId);
  95. merchantOrdersVO.setMerchantUserId(userId);
  96. merchantOrdersVO.setAmount(realPayAmount + postAmount);
  97. merchantOrdersVO.setPayMethod(payMethod);
  98. // 5. 构建自定义订单 vo
  99. OrderVO orderVO = new OrderVO();
  100. orderVO.setOrderId(orderId);
  101. orderVO.setMerchantOrdersVO(merchantOrdersVO);
  102. return orderVO;
  103. }
  104. }