《苍穹外卖》电商实战项目实操笔记系列(P66~P122)【中】

2024-02-18 16:50

本文主要是介绍《苍穹外卖》电商实战项目实操笔记系列(P66~P122)【中】,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

史上最完整的《苍穹外卖》项目实操笔记系列【中篇】,跟视频的每一P对应,全系列10万字,涵盖详细步骤与问题的解决方案。如果你操作到某一步卡壳,参考这篇,相信会带给你极大启发。

《苍穹外卖》项目实操笔记【上】:P1~P65《苍穹外卖》项目实操笔记【上】

《苍穹外卖》项目实操笔记【下】:P123~P189《苍穹外卖》项目实操笔记【下】

 一、微信小程序篇

1.1 本章介绍 P66

本章思路:HttpClient -> 微信小程序开发 -> 微信登录 -> 导入商品浏览功能代码

在微信登录实现的时候需要HttpClient。要开发用户端的微信小程序,方便用户点餐。

1.2 (HttpClient)介绍 P67

介绍:在Java中通过编码的方式发送HTTP请求。

maven坐标:

<dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId><version>4.5.13</version>
</dependency>

1.3 (HttpClient)发GET请求 P68

要保证当前项目已经提前启动好了。

在key-server/src/test/java/com/sky/test下面创建HttpClientTest类然后写入如下代码:

public class HttpClientTest{@Testpublic void testGET() throws Exception {//创建httpclient对象CloseableHttpClient httpClient = HttpClients.createDefault();//创建请求对象HttpGet httpGet = new HttpGet("http://localhost:8080/user/shop/status");//发送请求CloseableHttpResponse response = httpClient.execute(httpGet);//获取服务端返回的状态码int statusCode = response.getStatusLine().getStatusCode();System.out.println("服务端返回的状态码为:"+statusCode);//获取服务端返回的数据HttpEntity entity = response.getEntity();String body = EntityUtils.toString(entity);System.out.println("服务端返回的数据为:"+body);//关闭资源response.close();httpClient.close();}
}

 效果图被删了。

1.4 (HttpClient)发POST请求 P69

要保证当前项目已经提前启动好了。POST需要提前传入参数。

在上一节的HttpClientTest类中写入如下的代码:

@Test
public void testPOST() throws Exception{//创建httpclient对象CloseableHttpClient httpClient = HttpClients.createDefault();//创建请求对象HttpPost httpPost = new HttpPost("http://localhost:8080/admin/employee/login");JSONObject jsonObject = new JSONObject();jsonObject.put("username","admin");jsonObject.put("password","123456");StringEntity entity = new StringEntity(jsonObject.toString());//指定请求编码方式entity.setContentEncoding("utf-8");//数据格式entity.setContentType("application/json");httpPost.setEntity(entity);//发送请求CloseableHttpResponse response = httpClient.execute(httpPost);int statusCode = response.getStatusLine().getStatusCode();System.out.println("响应码为:"+statusCode);HttpEntity entity1 = response.getEntity();String body = EntityUtils.toString(entity1);System.out.println("响应数据为:"+body);//关闭资源response.close();httpClient.close();}

 与Get不同的是,这里添加了登录的参数以及请求编码的方式和数据的格式,其它的基本都是相同的。

这个其实是调用了登录的API,最后会返回一个Token。

1.5 (微信小程序)介绍 P70

首先要注册,个人方式注册无法开通支付权限。小程序信息完善。开发小程序。提交审核和发布。

开发支持:开发文档,开发者工具,设计指南,小程序体验DEMO。

1.6 (微信小程序)准备 P71

准备工作:①注册小程序,②完善小程序信息,③下载开发者工具。

小程序 (qq.com)

然后在设置里的基本设置栏输入小程序基本的信息。

在开发管理菜单可以获取APPID和小程序秘钥:

AppID(小程序ID):wx3910b10fd7db38d1

AppSecret(小程序密钥):f274884c2a56466016ebbcf009404e63

下载微信开发者工具。

也可以直接使用资料中的安装包。

扫一扫登录,点击+号创建小程序,注意填写AppId:

下面是开发界面:

可以点击选择上面的面板,点击详情-本地设置-勾选不校验合法域名(因为在开发阶段不勾选会导致请求发送不出去):

1.7 (微信小程序)入门1  P72

app.js存放的是业务代码,小程序的逻辑。

app.json存放的是配置文件,小程序的公共配置。

app.wxss是小程序的公共样式表。

上述这些会放在pages目录下面。

wxml是小程序的页面结构。

1.8 (微信小程序)入门2 P73

效果:点击获取用户信息就会弹出申请框,点击允许之后,就可以显示昵称和头像图片:

​ 

首先要清空pages/index下面的index.js和index.wxml页面中的内容。

index.wxml中的代码如下:

<view class="container"><view>{{msg}}</view><view><button bindtap="getUserInfo" type="warn">获取用户信息</button>昵称: {{nickName}}<image style="width: 100px;height: 100px;" src="{{url}}"></image></view>
</view>

这个文件主要是负责定义前端展示的元素,比如{{}}是文本内插的符号,按钮用<button>标签,bindtap里面定义的是触发的事件,图片用<image>标签。

type为default显示的是绿色。primary为绿色外框白色文字。warn为白色外框红色文字。

index.js中的代码如下: 

Page({data:{msg:'hello world',nickName: '',url:''},//获取微信用户的头像和昵称getUserInfo(){wx.getUserProfile({desc: '获取用户信息',success: (res)=>{console.log(res.userInfo);//为数据赋值this.setData({nickName: res.userInfo.nickName,url: res.userInfo.avatarUrl})}})}
})

 这个文件就是具体逻辑代码,比如data用于定于变量,然后getUserInfo就是上面定义的触发事件。

1.9 (微信小程序)入门3 P74

前端的效果:点击微信登录,会显示授权码。在控制台会输出每次的授权码。

index.mxml代码如下:

<view class="container"><view>{{msg}}</view><view><button bindtap="getUserInfo" type="warn">获取用户信息</button>昵称: {{nickName}}<image style="width: 100px;height: 100px;" src="{{url}}"></image></view><view><button bindtap="wxLogin" type="warn">微信登录</button>授权码:{{code}}</view>
</view>

 index.js代码如下:

Page({data:{msg:'hello world',nickName: '',url:'',code:''},//获取微信用户的头像和昵称getUserInfo(){wx.getUserProfile({desc: '获取用户信息',success: (res)=>{console.log(res.userInfo);//为数据赋值this.setData({nickName: res.userInfo.nickName,url: res.userInfo.avatarUrl})}})},//微信登录,获取微信用户的授权码wxLogin(){wx.login({success : (res)=>{console.log(res.code)this.setData({code:res.code})}})}
})

1.10 (微信小程序)入门4 P75

点击发送请求会向http://localhost:8080/user/shop/status发送查询,返回店铺状态。

index.js完整代码如下:

Page({data:{msg:'hello world',nickName: '',url:'',code:''},//获取微信用户的头像和昵称getUserInfo(){wx.getUserProfile({desc: '获取用户信息',success: (res)=>{console.log(res.userInfo);//为数据赋值this.setData({nickName: res.userInfo.nickName,url: res.userInfo.avatarUrl})}})},//微信登录,获取微信用户的授权码wxLogin(){wx.login({success : (res)=>{console.log(res.code)this.setData({code:res.code})}})},//发送请求sendRequest(){wx.request({url: 'http://localhost:8080/user/shop/status',method:'GET',success:(res)=>{console.log(res.data) //data代表整个响应回来的数据}})}
})

index.wxml完整代码如下: 

<view class="container"><view>{{msg}}</view><view><button bindtap="getUserInfo" type="warn">获取用户信息</button>昵称: {{nickName}}<image style="width: 100px;height: 100px;" src="{{url}}"></image></view><view><button bindtap="wxLogin" type="warn">微信登录</button>授权码:{{code}}</view><view><button bindtap="sendRequest" type="default">发送请求</button></view>
</view>

1.11 (微信小程序)发布 P76

点击右上角的微信开发者工具:

点击上传按钮:

然后可以在版本管理界面看到,开发版本多了一项:

1.12 (微信登录)代码导入 P77

小程序代码是在day06文件夹中,需要事先解压一下:

然后在小程序开发助手点击导入:

这里要确保AppID是自己的:

1.13 (微信登录)登录流程 P78

下面是微信登录的官方文档:

开放能力 / 用户信息 / 小程序登录 (qq.com)

点击上面的链接可以进入到如下界面:

下面是请求的参数:

使用PostMan进行测试,成功:

也可以直接在浏览器地址栏中构建:

微信登录流程:小程序要调用wx.login获取code授权码,然后要调研wx.request()发送code授权码。开发者服务器要向微信接口服务提交appid+appsecret+code,然后就可以获得session_key(会话密钥)和openid。然后开发者服务器可以自定义登录状态,产生一个token令牌,然后返回自定义登录状态。小程序可以把token令牌进行存储。然后wx.request可以法器业务请求,开发者服务器可以解析token,最终返回数据。

1.14 (微信登录)分析设计 P79

小程序给后端发送授权码code,然后后端去调微信的接口服务,后端再给用户返回令牌,令牌里包含用户的唯一标识。

数据库设计如下:

1.15 (微信登录)代码开发 P80

在applicaton-dev.yml中写入如下代码:

sky:wechat:appid: wx3910b10fd7db38d1secret: f274884c2a56466016ebbcf009404e63

 在application.yml中写入如下代码:

sky:wechat:appid: ${sky.wechat.appit}secret: ${sky.wechat.secret}

在sky: jwt:下面加上如下代码:

user-secret-key: itheima
user-ttl: 7200000
user-token-name: authentication

1.16 (微信登录)代码开发 P81

在sky-server的controller下的user下创建UserController,写入如下代码:

@RestController
@RequestMapping("/user/user")
@Api(tags="C端用户相关接口")
@Slf4j
public class UserController {@Autowiredprivate UserService userService;@Autowiredprivate JwtProperties jwtProperties;//微信登录@PostMapping("/login")@ApiOperation("微信登录")public Result<UserLoginVO> login(@RequestBody UserLoginDTO userLoginDTO){log.info("微信用户登录;{}",userLoginDTO.getCode());//微信登录User user = userService.wxLogin(userLoginDTO);//为微信用户生成jwt令牌Map<String,Object> claims =  new HashMap<>();claims.put(JwtClaimsConstant.USER_ID,user.getId());String token = JwtUtil.createJWT(jwtProperties.getUserSecretKey(),jwtProperties.getUserTtl(),claims);UserLoginVO userLoginVO = UserLoginVO.builder().id(user.getId()).openid(user.getOpenid()).token(token).build();return Result.success(userLoginVO);}
}

 然后在sky-server的service下创建UserService,写入如下代码:

public interface UserService {//微信登录User wxLogin(UserLoginDTO userLoginDTO);
}

1.17  (微信登录)代码开发 P82

本节是在上节的基础上继续对service的实现类进行具体的实现:

在service曾的impl包下创建UserServiceImpl实现类,写入如下的代码:

@Service
@Slf4j
public class UserServiceImpl implements UserService {//微信服务接口地址public static final String WX_LOGIN="http://api.weixin.qq.com/sns/jscode2session";@Autowiredprivate WeChatProperties weChatProperties; //配置文件中传入的值会被放入配置类,所以只需要自动注入然后获取即可@Autowiredprivate UserMapper userMapper;//微信登录public User wxLogin(UserLoginDTO userLoginDTO){String openid = getOpenid(userLoginDTO.getCode());//判断openid是否为空,如果为空登录失败,抛出业务异常if(openid==null){throw new LoginFailedException(MessageConstant.LOGIN_FAILED);}//判断当前用户是否为新用户(拿openid到用户表里查)User user = userMapper.getByOpenid(openid);//如果是新用户,自动完成注册if(user == null){user = User.builder().openid(openid).createTime(LocalDateTime.now()).build();userMapper.insert(user);}//返回这个用户对象return user;}private String getOpenid(String code){//调用微信接口服务,来获得当前微信用户的openidMap<String,String> map = new HashMap<>();map.put("appid",weChatProperties.getAppid());map.put("secret",weChatProperties.getSecret());map.put("js_code",code);map.put("grant_type","authorization_code");String json = HttpClientUtil.doGet(WX_LOGIN,map);JSONObject jsonObject = JSON.parseObject(json);String openid = jsonObject.getString("openid");return openid;}
}

主要的功能是:获取当前微信用户的openId,然后判断当前用户是否为新用户,如果是新用户就自动调用Mapper层完成注册。

在mapper层的UserMapper类中写入如下代码:

//插入数据
void insert(User user);

在resources下的mapper下的UserMapper.xml中写入Insert的具体代码:

<?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.sky.mapper.UserMapper"><insert id="insert" useGeneratedKeys="true" keyProperty="id">insert into user(openid,name,phone,sex,id_number,avatar,create_time)values (#{openid},#{name},#{phone},#{sex},#{idNumber},#{avatar},#{createTime})</insert>
</mapper>

1.18 (微信登录)功能测试+拦截器 P83

在UserController中的log.info处加一个断点,然后点击小虫,进行断点调试。

然后在微信开发助手点击编译,然后确定授权微信,然后点击允许获得昵称和头像,此时控制台会获得一个code,然后到controller传入的参数DTO看看是否也获得到了code。

在service的UserServiceImpl中的log上加入一个断点。因为是新用户,所以会执行插入操作,插入openid和时间。

然后取消所有断点,重新运行。看开发者工具可以看到正常获得authentication。

现在需要配置一个拦截器来识别authentication实现拦截放行。

在sky-server下的interceptor将JwtTokenAdminInterceptor复制一个然后粘贴到该目录的下面,重新命名为JwtTokenUserInterceptor:

@Component
@Slf4j
public class JwtTokenUserInterceptor implements HandlerInterceptor {@Autowiredprivate JwtProperties jwtProperties;//校验jwtpublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//判断当前拦截到的是Controller的方法还是其他资源if (!(handler instanceof HandlerMethod)) {//当前拦截到的不是动态方法,直接放行return true;}//1、从请求头中获取令牌String token = request.getHeader(jwtProperties.getUserTokenName());//2、校验令牌try {log.info("jwt校验:{}", token);Claims claims = JwtUtil.parseJWT(jwtProperties.getUserSecretKey(), token);Long userId = Long.valueOf(claims.get(JwtClaimsConstant.USER_ID).toString());BaseContext.setCurrentId(userId);log.info("当前员工id:", userId);//3、通过,放行return true;} catch (Exception ex) {//4、不通过,响应401状态码response.setStatus(401);return false;}}
}

然后要让这个拦截器生效:

在sky-server下的config包中的WebMvcConfiguration中写入如下代码:

@Autowired
private JwtTokenUserInterceptor jwtTokenUserInterceptor;

 在addInterceptors中写入如下代码(注意下面addInterceptor里面是jwtTokenUserInterceptor):

registry.addInterceptor(jwtTokenUserInterceptor).addPathPatterns("/user/**").excludePathPatterns("/user/user/login").excludePathPatterns("/user/shop/status");

【如果有需要现成代码欢迎点赞+收藏后私信我】 

1.19 (商品浏览)需求分析 P84

四个接口:查询分类;根据分类id查询菜品;根据分类id查询套餐;根据套餐id查询包含的菜品。

1.20 (商品浏览)代码导入 P85

这里尤其要注意,要把day04中项目实战-套餐管理-参考答案全部拷到controller层的admin下,注意一定要是admin包下(不能是user包)的对应controller类中。

这个参考答案是关于套餐的代码,对后面的操作很重要!(需要导入的代码略多,但请耐心!)

当我全部把代码导入后,发现SetmealMapper.xml下好像少了一个update,代码我补充在下面:

<update id="update">update setmeal<set><if test="categoryId != null"> category_Id = #{categoryId},</if><if test="name != null">name = #{name},</if><if test="price != null">price = #{price},</if><if test="status != null">status = #{status},</if><if test="description != null">description = #{description},</if><if test="image != null">image = #{image},</if></set>where id = #{id}
</update>

然后像我一样随便设置一个套餐。

然后检查一下起售停售、新增、删除、修改、批量删除、查询这些功能是否正常,我这里是都正常。

然后在资料包中day06/代码导入/商品浏览中有9个文件:3个controller,2个service,2个serviceImpl,1个mapper,1个mapper.xml。

注意下面2点:

1.上面的controller文件都要放在controller的user这个包下面。

2.有些已有的service或者mapper仅仅只需要拷贝方法,有些缺少的需要新建。(这里list可能会报错看所给资料的day04中有项目实战-套餐管理 - 参考答案,把list的mapper层的代码拷贝过来即可。)

拷贝完在IDEA编译一下,然后到小程序再编译一下。可以看到小程序中已经出现各类菜品(图片没显示属于正常,下面我详细说),然后会有选规格。

然后点击套餐,可以看到套餐里面包含的菜品。

出现问题:

1.如果微信前端没有显示任何东西,看微信开发助手控制台的list是401的问题:一般是拦截器的设置有问题,首先把拦截器给注视掉(可以参考前面的1.18 (微信登录)功能测试+拦截器 P83)。重新启动看能不能访问到。如果能访问到再参考P83的拦截器重新进行设置。

2.如果微信前端的图片是403的问题:这里先说明,图片不显示是正常的——因为像这些图片都是存储在老师阿里云的oss上的,可能老师更改了权限,所以我们没有权限访问图片。

参考下面的URL可以发现,微信开发助手的图片链接不是我当前的oss服务器地址。

解决方法很简单,在菜品修改界面,修改菜品的图片即可(相当于重新上传到你自己的oss服务器上)。如下图左图是修改前,右图是修改后。

​ 

二、缓存商品和购物车篇

2.1 本章内容介绍 P86

菜品和套餐存储在数据库中,如果短期内有大量的人查询会导致数据库压力过大,用户体验不佳。现在把商品数据缓存到Redis中。

加入购物车,查看购物车,可以看到购物车中的商品,可以清空购物车,还可以添加商品到购物车。

缓存菜品 -> 缓存套餐 -> 添加购物车 -> 查看购物车 -> 清空购物车,从购物车中减去某个商品。

2.2 (缓存菜品)设计分析 P87

问题说明:小程序菜品数据是通过数据库获得,如果用户端访问量过大,数据库的压力会增加。

实现思路:通过Redis来缓存菜品数据,减少数据库查询操作。内存操作的性能比磁盘IO性能更高。

每个分类下的菜品保存一份缓存数据。

数据库中菜品数据有变更时要清理缓存数据。

2.3 (缓存菜品)代码开发 P88

在sky-server下的controller/user下的DishController类中写入如下代码:

@RestController("userDishController")
@RequestMapping("/user/dish")
@Slf4j
@Api(tags = "C端-菜品浏览接口")
public class DishController {@Autowiredprivate DishService dishService;@Autowiredprivate RedisTemplate redisTemplate;// 根据分类id查询菜品@GetMapping("/list")@ApiOperation("根据分类id查询菜品")public Result<List<DishVO>> list(Long categoryId) {//构造redis中的key,规则:dish_分类idString key = "dish_" + categoryId;//查询redis中是否存在菜品数据List<DishVO> list = (List<DishVO>)redisTemplate.opsForValue().get(key);if(list != null && list.size()>0){//如果存在,直接返回,无须查询数据库return Result.success(list);}Dish dish = new Dish();dish.setCategoryId(categoryId);dish.setStatus(StatusConstant.ENABLE);//查询起售中的菜品//如果不存在,查询数据库,将查询到的数据放入redis中list = dishService.listWithFlavor(dish);redisTemplate.opsForValue().set(key,list);return Result.success(list);}
}

 然后在小程序助手中编译,然后在不同的菜类间切换(比如蜀味烤鱼和蜀味牛蛙),效果是第一次IDEA的控制台会输出SQL语句,然后去看Redis会发现已经有了缓存。然后继续在前面已缓存的菜类间切换,会发现控制台不再输出SQL语句,说明走的是Redis缓存。

2.4 (缓存菜品)清理缓存 p89

不清理可能出现的问题:比如菜品的价格如果被修改,如果继续从Redis从取数据,会导致数据的不一致。

新增菜品、修改菜品、批量删除菜品、起售和停售菜品的时候需要清理缓存。

所以需要在controller下的admin中的DishController中修改代码:

@Autowired
private RedisTemplate redisTemplate;
//清理缓存数据
private void cleanCache(String pattern){Set keys = redisTemplate.keys(pattern);redisTemplate.delete(keys);
}

在update、delete方法中调用完service的方法后:

cleanCache("dish_*");

 在save方法中调用service的方法后:

String key = "dish_" + dishDTO.getCategoryId();
cleanCache(key);//清理缓存数据

2.5 (缓存菜品)功能测试 P90

首先把所有的菜类都缓存到Redis中(在小程序助手里把所有菜类都点一遍),然后在电脑管理端的前端平台修改某一道菜,然后再看Redis的可视化面板,看看是不是所有的Redis都被清空。

2.6 SpringCache介绍 P91

SpringCache是Spring提供的缓存框架。提供了基于注解的缓存功能。

SpringCache提供了一层抽象,底层可以切换不同的缓存实现(只需要导入不同的Jar包即可),如EHCache,Caffeine,Redis。

2.7 SpringCache入门 P92

首先打开下面这个项目,然后配置一下配置文件:

​ 

找到项目提供的.sql文件,然后在Navicat中新建一个spring_cache_demo的数据库,然后用查询语句查询即可:

下面2个重要依赖已经导入:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2.8 SpringCache入门 P93

在启动类上加@EnableCaching

在controller层的UserController下的save方法上写入如下的代码:

@PostMapping
@CachePut(cacheNames="userCache",key="#user.id") //如果使用spring Cache缓存数据,key的生成:userCache::1。user是从参数取到的。
//@CachePut(cacheNames="userCache",key="#result.id") //result是从返回值return取到的
//@CachePut(cacheNames="userCache",key="#p0.id")
//@CachePut(cacheNames="userCache",key="#a0.id")
//@CachePut(cacheNames="userCache",key="#root.args[0].id")
public User save(@RequestBody User user){userMapper.insert(user);return user;
}

注意key="#result.id"中的result取的是返回值返回的那个结果。 key="#user.id"的user取的是传入的参数。p0,a0,root.args[0]表示取的都是第1个参数。

现在进入localhost:8888/doc.html中对save方法发送请求:

然后可以看到缓存中有了数据:

可以看到目录是树状结构,树状结构用:号分隔。

2.9 SpringCache入门 P94

在controller层的UserController下的getById方法上写入如下的代码:

@Cacheable(cacheNames="userCache",key="#id")

然后在方法体的第1行打上断点,点击小虫,然后到localhost:8888/doc.html中对get方法进行测试,发送1,效果是:直接从Redis中返回数据,压根不会触发断点。

现在手动删除id为1的数据,然后重新在doc.html中发送数据,然后会执行断点,放心后控制台输出SQL语句,然后Redis缓存中也有数据。

2.10 SpringCache入门 P95

在controller层的UserController下的deleteById和deleteAll方法上加入如下注解:

@DeleteMapping
@CacheEvict(cacheNames = "userCache",key="#id") //key的形式 userCache::10
public void deleteById(Long id){userMapper.deleteById(id);}@DeleteMapping("/delAll")
@CacheEvict(cacheNames="userCache",allEntries = true)
public void deleteAll(){userMapper.deleteAll();
}

测试的话可以在两个方法内的第1行打上断点,点击小虫,然后到localhost:8888/doc.html中对delete和deleteAll方法进行测试,先通过getById方法增加几个数据,然后再逐一删除。

可以这么理解cacheNames里的参数就是指定key的名字,只会删除相应的key。

2.11 (缓存套餐)代码开发 P96

首先要在sky-server包下导入下面两个坐标:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId>
</dependency>

然后要在sky-server包下的启动类SkyApplication类上加如下注解:

@EnableCaching

首先是在controller/user包下的SetmealController类中的list方法上加如下注解:

@Cacheable(cacheNames="setmealCache",key="#categoryId") //key: setmealCache::100

然后是在controller/admin包下的SetmealController类中的save方法上加如下注解:

@CacheEvict(cacheNames="setmealCache",key="#setmealDTO.categoryId")

最后是在controller/admin包下的SetmealController类中的update、delete、startOrStop方法上加如下注解:

@CacheEvict(cacheNames = "setmealCache",allEntries = true)

2.12 (缓存套餐)功能测试 P97

首先IDEA和小程序助手都要启动。然后先在小程序助手点套餐,然后Redis会把数据缓存。

这个时候到管理端前端界面(电脑网页端),尝试起售和禁售,看看Redis缓存会不会被清空,然后再尝试修改和删除同样也会触发Redis清空。

2.13 (添购物车)分析设计 P98

购物车:暂时存放所选商品的地方。需要记录:选的是什么商品,选购商品的个数。不同用户有不同购物车。

没设置口味数据直接点击+号就可以加入购物车;如果有口味数据,就点击选择规格然后选择口味加入购物车。

如下图有许多冗余字段,目的是为了提高数据的显示速度,不必去连接其它表查询。

冗余字段不能太多,也不能经常变动。

2.14 (添购物车)代码开发 P99

老师在这里说了一下:返回类型Result<T>和Result的区别,如果接口设计里面,对返回的data数据没有要求,则不用写泛型T。

在sky-server的controller的user中创建一个名为ShoppingCartController类,写入如下代码:

@RestController
@RequestMapping("/user/shoppingCart")
@Slf4j
@Api(tags="C端购物车相关接口")
public class ShoppingCartController {@Autowiredprivate ShoppingCartService shoppingCartService;@PostMapping("/add")@ApiOperation("添加购物车")public Result add(@RequestBody ShoppingCartDTO shoppingCartDTO){log.info("添加购物车,商品信息为:{}",shoppingCartDTO);shoppingCartService.addShoppingCart(shoppingCartDTO);return Result.success();}
}

然后在sky-server的service下创建ShoppingCartService类,写入如下代码:

public interface ShoppingCartService {//添加购物车void addShoppingCart(ShoppingCartDTO shoppingCartDTO);
}

 然后在sky-server的service的Impl下创建ShoppingCartServiceImpl类:

@Service
@Slf4j
public class ShoppingCartServiceImpl implements ShoppingCartService {@Overridepublic void addShoppingCart(ShoppingCartDTO shoppingCartDTO) {//判断当前加入购物车中的商品是否已经存在了//如果已经存在,只需要数量+1//如果不存在,则需要插入一条购物车数据}
}

 更多细节放在下节完善。

2.15(添购物车)开发 P100

 完善sky-server的service的Impl下的ShoppingCartServiceImpl类:

@Service
@Slf4j
public class ShoppingCartServiceImpl implements ShoppingCartService {@Autowiredprivate ShoppingCartMapper shoppingCartMapper;@Overridepublic void addShoppingCart(ShoppingCartDTO shoppingCartDTO) {//判断当前加入购物车中的商品是否已经存在了ShoppingCart shoppingCart = new ShoppingCart();BeanUtils.copyProperties(shoppingCartDTO,shoppingCart);//userid暂时还不知道,从ThreadLocal取Long userId = BaseContext.getCurrentId();shoppingCart.setUserId(userId);List<ShoppingCart> list = shoppingCartMapper.list(shoppingCart);//如果已经存在,只需要数量+1if(list != null && list.size()>0) {ShoppingCart cart = list.get(0);cart.setNumber(cart.getNumber()+1);shoppingCartMapper.updateNumberById(cart);}//如果不存在,则需要插入一条购物车数据}
}

 在sky-server的mapper下的ShoppingCartMapper类中写入如下代码:

@Mapper
public interface ShoppingCartMapper {List<ShoppingCart> list(ShoppingCart shoppingCart);//根据id修改商品数量@Update("update shopping_cart set number = #{number} where id = #{id}")void updateNumberById(ShoppingCart shoppingCart);
}

  在sky-server的resources下的mapper下的ShoppingCartMapper.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.sky.mapper.ShoppingCartMapper"><select id="list" resultType="com.sky.entity.ShoppingCart">select * from shopping_cart<where><if test="userId != null">and user_id = #{userId}</if><if test="setmealId != null">and setmeal_id = #{setmealId}</if><if test="dishId != null">and dish_id = #{dishId}</if><if test="dishFlavor != null">and dish_flavor = #{dishFlavor}</if></where></select></mapper>

2.16(添购物车)开发 P101

 完善sky-server的service的Impl下的ShoppingCartServiceImpl类:

@Service
@Slf4j
public class ShoppingCartServiceImpl implements ShoppingCartService {@Autowiredprivate ShoppingCartMapper shoppingCartMapper;@Autowiredprivate DishMapper dishMapper;@Autowiredprivate SetmealMapper setmealMapper;@Overridepublic void addShoppingCart(ShoppingCartDTO shoppingCartDTO) {//判断当前加入购物车中的商品是否已经存在了ShoppingCart shoppingCart = new ShoppingCart();BeanUtils.copyProperties(shoppingCartDTO,shoppingCart);//userid暂时还不知道,从ThreadLocal取Long userId = BaseContext.getCurrentId();shoppingCart.setUserId(userId);List<ShoppingCart> list = shoppingCartMapper.list(shoppingCart);//如果已经存在,只需要数量+1if(list != null && list.size()>0) {ShoppingCart cart = list.get(0);cart.setNumber(cart.getNumber()+1);shoppingCartMapper.updateNumberById(cart);}else {//如果不存在,则需要插入一条购物车数据//判断本次添加到购物车的是菜品还是套餐,因为要查询不同表Long dishId = shoppingCartDTO.getDishId();if(dishId!=null){//本次添加到购物车的是菜品Dish dish = dishMapper.getById(dishId);shoppingCart.setName(dish.getName());shoppingCart.setImage(dish.getImage());shoppingCart.setAmount(dish.getPrice());}else{//本次添加到购物车的是套餐Long setmealId = shoppingCartDTO.getSetmealId();Setmeal setmeal = setmealMapper.getById(setmealId);shoppingCart.setName(setmeal.getName());shoppingCart.setImage(setmeal.getImage());shoppingCart.setAmount(setmeal.getPrice());}shoppingCart.setNumber(1);shoppingCart.setCreateTime(LocalDateTime.now());shoppingCartMapper.insert(shoppingCart);}}
}

在shoppingCartMapper中添加insert方法:

@Insert("insert into shopping_cart(name,user_id,dish_id,setmeal_id,dish_flavor,number,amount,image,create_time)" +"value (#{name},#{userId},#{dishId},#{setmealId},#{dishFlavor},#{number},#{amount},#{image},#{createTime})")
void insert(ShoppingCart shoppingCart);

2.17(添购物车)测试 P102

 可能会遇到2个问题:1.数据没插入到数据库,2.userid为空。

先说1,数据没插入到数据库的根本原因在于userid为空,因为如下图user_id默认不为空,假如userid为空则无法正常插入数据, 而userid为空的根源在于拦截器的问题。

拦截器如果有问题可以参考我前面1.18 (微信登录)功能测试+拦截器 P83进行自查,特别注意下面这个地方(可能大概率写错在这个地方):

registry.addInterceptor(jwtTokenUserInterceptor)

 额外说一点,如果有出现下面问题

2024-01-25 01:42:12.490 ERROR 44380 --- [nio-8080-exec-7] c.a.druid.pool.DruidAbstractDataSource   : discard long time none received connection. , jdbcUrl : jdbc:mysql://localhost:3306/sky_take_out?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true, jdbcUrl : jdbc:mysql://localhost:3306/sky_take_out?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true, lastPacketReceivedIdleMillis : 97208

可以在启动类中加入如下静态代码:

static {System.setProperty("druid.mysql.usePingMethod","false");}

如果传入的参数为空重点看一下有没有在传入的参数前加@RequestBody注解。 

2.18 查看购物车 P103

在sky-server的controller下的ShoppingCartController类中加入如下代码:

@ApiOperation("查看购物车")
@GetMapping("/list")
public Result<List<ShoppingCart>> list(){List<ShoppingCart> list = shoppingCartService.showShoppingCart();return Result.success(list);
}

 在sky-server的service下的ShoppingCartService类中加入如下代码:

List<ShoppingCart> showShoppingCart();

 在sky-server的service的Impl下的ShoppingCartServiceImpl类中加入如下代码:

@Override
public List<ShoppingCart> showShoppingCart() {Long userId = BaseContext.getCurrentId();ShoppingCart shoppingCart = ShoppingCart.builder().userId(userId).build();List<ShoppingCart> list = shoppingCartMapper.list(shoppingCart);//只需要传userid即可return list;
}

大致的效果如下图:

2.19 清空购物车 P104

在sky-server的controller下的ShoppingCartController类中加入如下代码:

@ApiOperation("清空购物车")
@DeleteMapping("/clean")
public Result clean(){shoppingCartService.clean();return Result.success();
}

 在sky-server的service下的ShoppingCartService类中加入如下代码:

//清空购物车
void clean();

 在sky-server的service的Impl下的ShoppingCartServiceImpl类中加入如下代码:

//清空购物车
public void clean() {Long userId = BaseContext.getCurrentId();shoppingCartMapper.deleteByUserId(userId);
}

 在sky-server的mapper的ShoppingCartMapper类中加入如下代码:

@Delete("delete from shopping_cart where user_id=#{userId}")
void deleteByUserId(Long userId);

到这里如果有同学需要项目代码的话,欢迎【点赞+收藏后私信我领取】

三、用户下单支付篇

3.1 本章介绍 P105

本章完成的是用户下单和订单支付功能。

导入地址簿功能代码 -> 用户下单 ->订单支付。

3.2 (地址簿)分析设计 P106

一个用户可以有多个收货地址,只能有一个默认地址。

点击新增收货地址可以新增一个地址。

还可以修改和删除收货地址。

3.3 (地址簿)代码导入 P107

直接把day08/地址簿模块功能代码导入即可,注意要把AddressController放到user包下,其它很简单。

3.4 (地址簿)功能测试 P108

通过小程序助手前端测试一下新增、修改、删除、设置默认地址等功能。

3.5 (用户下单)分析设计 P109

接口设计:

3.6 (用户下单)分析设计 P110

数据库设计无需多言。

3.7 (用户下单)代码开发 P111

在sky-server的controller下创建OrderController类中加入如下代码:

@RestController("userOrderController")
@RequestMapping("/user/order")
@Api(tags="用户端订单相关接口")
@Slf4j
public class OrderController {@Autowiredprivate OrderService orderService;@PostMapping("/submit")@ApiOperation("用户下单")public Result<OrderSubmitVO> submit(@RequestBody OrdersSubmitDTO orderSubmitDTO){log.info("用户下单,参数为:{}",orderSubmitDTO);OrderSubmitVO orderSubmitVO = orderService.submitOrder(orderSubmitDTO);return Result.success(orderSubmitVO);}
}

  在sky-server的service下创建OrderService类中加入如下代码:

public interface OrderService {//用户下单OrderSubmitVO submitOrder(OrdersSubmitDTO orderSubmitDTO);
}

  在sky-server的service的Impl下创建OrderServiceImpl类中加入如下代码:

public class OrderServiceImpl implements OrderService {@Autowiredprivate OrderMapper orderMapper;@Autowiredprivate OrderDetailMapper orderDetailMapper;public OrderSubmitVO submitOrder(OrdersSubmitDTO orderSubmitDTO){return null;}
}

  在sky-server的mapper下创建OrderMapper类中加入如下代码:

@Mapper
public interface OrderMapper {
}

  在sky-server的mapper下创建OrderDetailMapper类中加入如下代码:

@Mapper
public interface OrderDetailMapper {
}

3.8 (用户下单)代码开发 P112

本节主要是对OrderService进行完善,主要完善了处理各种业务异常的情况

public class OrderServiceImpl implements OrderService {@Autowiredprivate OrderMapper orderMapper;@Autowiredprivate OrderDetailMapper orderDetailMapper;@Autowiredprivate AddressBookMapper addressBookMapper;@Autowiredprivate ShoppingCartMapper shoppingCartMapper;public OrderSubmitVO submitOrder(OrdersSubmitDTO orderSubmitDTO){//处理各种业务异常(地址簿为空,购物车数据为空)//地址簿为空AddressBook addressBook = addressBookMapper.getById(orderSubmitDTO.getAddressBookId());if(addressBook == null){//抛出业务异常throw new AddressBookBusinessException(MessageConstant.ADDRESS_BOOK_IS_NULL);}//查询当前用户的购物车数据Long userId = BaseContext.getCurrentId();ShoppingCart shoppingCart = new ShoppingCart();shoppingCart.setUserId(userId);List<ShoppingCart> shoppingCartList = shoppingCartMapper.list(shoppingCart);if(shoppingCartList == null || shoppingCartList.size()==0){throw new ShoppingCartBusinessException(MessageConstant.ADDRESS_BOOK_IS_NULL);}//向订单表插入1条数据//向订单明细表插入n条数据//清空当前用户的购物车数据//封装VO返回结束return null;}
}

3.9 (用户下单)代码开发 P113

本节主要是对OrderService继续进行完善,然后创建orderMapper类,实现insert方法。

下面是对OrderService的完善:

public class OrderServiceImpl implements OrderService {@Autowiredprivate OrderMapper orderMapper;@Autowiredprivate OrderDetailMapper orderDetailMapper;@Autowiredprivate AddressBookMapper addressBookMapper;@Autowiredprivate ShoppingCartMapper shoppingCartMapper;public OrderSubmitVO submitOrder(OrdersSubmitDTO orderSubmitDTO){//1.处理各种业务异常(地址簿为空,购物车数据为空)//地址簿为空AddressBook addressBook = addressBookMapper.getById(orderSubmitDTO.getAddressBookId());if(addressBook == null){//抛出业务异常throw new AddressBookBusinessException(MessageConstant.ADDRESS_BOOK_IS_NULL);}//查询当前用户的购物车数据Long userId = BaseContext.getCurrentId();ShoppingCart shoppingCart = new ShoppingCart();shoppingCart.setUserId(userId);List<ShoppingCart> shoppingCartList = shoppingCartMapper.list(shoppingCart);if(shoppingCartList == null || shoppingCartList.size()==0){throw new ShoppingCartBusinessException(MessageConstant.ADDRESS_BOOK_IS_NULL);}//2.向订单表插入1条数据Orders orders = new Orders();BeanUtils.copyProperties(orderSubmitDTO,orders);orders.setOrderTime(LocalDateTime.now());orders.setPayStatus(Orders.UN_PAID);orders.setStatus(Orders.PENDING_PAYMENT);orders.setNumber(String.valueOf(System.currentTimeMillis()));orders.setPhone(addressBook.getPhone());orders.setConsignee(addressBook.getConsignee());orders.setUserId(userId);orderMapper.insert(orders);//3.向订单明细表插入n条数据//4.清空当前用户的购物车数据//5.封装VO返回结果}
}

 创建OrderMapper类,写入如下代码:

@Mapper
public interface OrderMapper {void insert(Orders orders);
}

 在resources的mapper包下创建OrderMapper.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.sky.mapper.OrderMapper"><insert id="insert" useGeneratedKeys="true" keyProperty="id">insert into orders (number,status,user_id,address_book_id,order_time,checkout_time,pay_method,pay_status,amount,remark,phone,address,consignee,estimated_delivery_time,delivery_status,pack_amount,tableware_number,tableware_status)values(#{number},#{status},#{userId},#{addressBookId},#{orderTime},#{checkoutTime},#{payMethod},#{payStatus},#{amount},#{remark},#{phone},#{address},#{consignee},#{estimatedDeliveryTime},#{deliveryStatus},#{packAmount},#{tablewareNumber},#{tablewareStatus})</insert>
</mapper>

3.10(用户下单)代码开发P114

本节主要是对OrderService的最终完善,然后创建orderDetailMapper类,实现insertBatch方法:

下面是OrderService的最终代码:

@Service
public class OrderServiceImpl implements OrderService {@Autowiredprivate OrderMapper orderMapper;@Autowiredprivate OrderDetailMapper orderDetailMapper;@Autowiredprivate AddressBookMapper addressBookMapper;@Autowiredprivate ShoppingCartMapper shoppingCartMapper;@Transactionalpublic OrderSubmitVO submitOrder(OrdersSubmitDTO orderSubmitDTO){//1.处理各种业务异常(地址簿为空,购物车数据为空)//地址簿为空AddressBook addressBook = addressBookMapper.getById(orderSubmitDTO.getAddressBookId());if(addressBook == null){//抛出业务异常throw new AddressBookBusinessException(MessageConstant.ADDRESS_BOOK_IS_NULL);}//查询当前用户的购物车数据Long userId = BaseContext.getCurrentId();ShoppingCart shoppingCart = new ShoppingCart();shoppingCart.setUserId(userId);List<ShoppingCart> shoppingCartList = shoppingCartMapper.list(shoppingCart);if(shoppingCartList == null || shoppingCartList.size()==0){throw new ShoppingCartBusinessException(MessageConstant.ADDRESS_BOOK_IS_NULL);}//2.向订单表插入1条数据Orders orders = new Orders();BeanUtils.copyProperties(orderSubmitDTO,orders);orders.setOrderTime(LocalDateTime.now());orders.setPayStatus(Orders.UN_PAID);orders.setStatus(Orders.PENDING_PAYMENT);orders.setNumber(String.valueOf(System.currentTimeMillis()));orders.setPhone(addressBook.getPhone());orders.setConsignee(addressBook.getConsignee());orders.setUserId(userId);orderMapper.insert(orders);List<OrderDetail> orderDetailList = new ArrayList<>();//3.向订单明细表插入n条数据for(ShoppingCart cart: shoppingCartList){OrderDetail orderDetail = new OrderDetail();//订单明细BeanUtils.copyProperties(cart,orderDetail);orderDetail.setOrderId(orders.getId());//设置当前订单明细关联的订单idorderDetailList.add(orderDetail);}orderDetailMapper.insertBatch(orderDetailList);//4.清空当前用户的购物车数据shoppingCartMapper.deleteByUserId(userId);//5.封装VO返回结果OrderSubmitVO orderSubmitVO = OrderSubmitVO.builder().id(orders.getId()).orderTime(orders.getOrderTime()).orderAmount(orders.getAmount()).build();return orderSubmitVO;}
}

创建orderDetailMapper类

@Mapper
public interface OrderDetailMapper {//批量插入订单明细数据void insertBatch(List<OrderDetail> orderDetailList);
}

在resources的mapper下创建orderDetailMapper类,实现insertBatch方法:

<?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.sky.mapper.OrderDetailMapper"><insert id="insertBatch">insert into order_detail(name,image,order_id,dish_id,setmeal_id,dish_flavor,number,amount)values<foreach collection="orderDetailList" item="od" separator=",">(#{od.name},#{od.image},#{od.orderId},#{od.dishId},#{od.setmealId},#{od.dishFlavor},#{od.number},#{od.amount})</foreach></insert>
</mapper>

3.11(用户下单)功能测试P115

在serviceImpl上加了@Transactional注解,所以Insert方法执行完后数据库还不会有数据,只有等方法执行完数据库才有数据。

如下图在小程序助手模拟下单,可以看到数据库里order_detail表会出现具体菜的细节。

orders表里会有相关的信息:

简单测试一下能获得相应信息即算成功。

到这里如果有同学需要项目代码的话,欢迎【点赞+收藏后私信我领取】

3.12(订单支付)介绍P116

大致讲了一下微信支付接入流程:

3.13(订单支付)介绍P117

本节主要讲微信支付的时序图:

mchid是商户号,out_trade_no是订单号,appid是应用的id,notify_url是回调地址,amount是总金额,payer是支付者,openid是当前付款用户的openid。

3.14(订单支付)介绍P118

本节主要介绍微信支付的相关接口,详见视频,在此不过多赘述。

3.15(订单支付)准备工作P119

这节涉及到2个问题:1.调用过程如何保证数据安全?2.微信后台如何调用到商户系统?

1.保证数据安全:微信支付涉及到数据的传输,为了保证数据的安全,所以需要对数据进行加密和解密,需要用到证书。

需要下面2个文件(要求小程序是企业认证,没有的话也没关系,对后面没太大影响):获取微信微信支付平台证书文件:apiclient_key.pem。商户私钥文件:wechatpay_166D96F876F45C7D07CE98952A96EC980368ACFC.pem

2.微信后台调用商户系统:当前商户系统的ip是笔记本的ip,仅仅只是局域网内的ip地址,要求获得公网ip,可以使用内网穿透技术解决。

获取临时域名

安装包在给的第8天资料里,这个软件是为了给当前局域网内的ip一个公网的域名:

在下面的地址栏输入cmd:

到官网获取隧道Authtoken:

输入如下代码,粘贴token即可:

会生成一个配置文件:

输入下面的代码:

cpolar.exe http 8080

获得临时的域名:

可以通过域名+doc.html进行访问:

3.16(订单支付)代码导入P120(必做)

在配置项application.yml中加入如下代码:

sky:wechat:appid: ${sky.wechat.appit}secret: ${sky.wechat.secret}mchid: ${sky.wechat.mchid}mchSerialNo: ${sky.wechat.mchid}privateKeyFilePath: ${sky.wechat.privateKeyFilePath}apiV3Key: ${sky.wechat.apiV3Key}weChatPayCertFilePath: ${sky.wechat.weChatPayCertFilePath}notifyUrl: ${sky.wechat.notifyUrl}refundNotifyUrl: ${sky.wechat.refundNotifyUrl}

仔细看下面参数及其对应的含义: 

在application-dev.yml文件中写入如下的代码(要注意notifyUrl和refundNotifyUrl,这两个url的前半部分都是cpolar临时生成的公网ip,因为是临时域名,所以每次生成的都会变化,以后要注意修改)

sky:wechat:appid: wx3910b10fd7db38d1secret: f274884c2a56466016ebbcf009404e63mchid: 1561414331mchSerialNo: 4B3B3DC35414AD50B1B755BAF8DE9CC7CF407606privateKeyFilePath: C:\software\apiclient_key.pemapiV3Key: CZBK51236435wxpay435434323FFDuv3weChatPayCertFilePat: C:\software\wechatpay_166D96F876F45C7D07CE98952A96EC980368ACFC.pemnotifyUrl: https://9ea0754.r19.cpolar.top/notify/paySuccessrefundNotifyUrl: https://9ea0754.r19.cpolar.top/notify/refundSuccess

 notifyUrl和refundNotifyUrl这俩地址是由微信后台请求的。

1. 将OrderController下的payment方法(订单支付)代码,复制到sky-server的controller的user下的OrderController中。

2. 将OrderService下的payment和paySuccess方法(订单支付,支付成功修改订单状态)代码,复制到sky-server的service下。

3. 将OrderServiceImpl下的payment和paySuccess方法(订单支付,支付成功修改订单状态)代码,复制到sky-server的service下的Impl中。还有注入下面的工具类。

@Autowired
private WeChatPayUtil weChatPayUtil;

4. 将OrderMapper下的getByNumber和update方法代码,复制到sky-server的Mapper下。

这个update方法一定要复制,后面章节频繁用到这个方法。

5. 直接把我下面完整版的update代码复制到sky-server的resources的mapper下的OrderMapper.xml中。

<update id="update" parameterType="Orders">update orders<set><if test="number != null"> number=#{number}, </if><if test="status != null"> status=#{status}, </if><if test="addressBookId != null"> address_book_id=#{addressBookId}, </if><if test="orderTime != null"> order_time=#{orderTime},</if><if test="checkoutTime != null"> checkout_time=#{checkoutTime}, </if><if test="payMethod != null"> pay_method=#{payMethod}, </if><if test="payStatus != null"> pay_status=#{payStatus}, </if><if test="amount != null"> amount=#{amount}, </if><if test="remark != null"> remark=#{remark}, </if><if test="phone != null"> phone=#{phone}, </if><if test="address != null"> address=#{address}, </if><if test="userName != null"> user_name=#{userName}, </if><if test="consignee != null"> consignee=#{consignee} ,</if><if test="cancelReason != null"> cancel_reason=#{cancelReason}, </if><if test="rejectionReason != null"> rejection_reason=#{rejectionReason}, </if><if test="cancelTime != null"> cancel_time=#{cancelTime}, </if><if test="estimatedDeliveryTime != null"> estimated_delivery_time=#{estimatedDeliveryTime}, </if><if test="deliveryStatus != null"> delivery_status=#{deliveryStatus}, </if><if test="deliveryTime != null"> delivery_Time=#{deliveryTime}, </if><if test="packAmount != null"> pack_amount=#{packAmount},</if><if test="tablewareNumber != null"> tableware_number=#{tablewareNumber}, </if><if test="tablewareStatus != null"> tableware_status=#{tablewareStatus}, </if></set>where id=#{id}</update>

6.将下面代码,复制到sky-server的Mapper下的UserMapper中:

@Select("select * from user where id=#{id}")
User getById(Long userId);

7. 最后在sky-server的controller下创建notify包,将PayNotifyController类复制到notify包下。

3.17(订单支付)解读代码P121

直接参考视频,在此不过多赘述。

3.18(订单支付)功能测试P122

支付成功效果如下:

因为没有营业执照没办法真正支付,所以下面打算直接绕过支付,直接支付成功。

需要修改2个地方:1个是微信小程序,点击支付按钮后直接跳转支付成功。另1个是后端,要求在收到前端支付操作后,不进行任何判断,直接给数据库设置已支付状态。

下面是修改过程:

首先在微信小程序里的pay包下的index.js中将如下的代码注释掉:

然后把原先注释掉的重定向解除:

然后在IDEA中,把service/impl下的OrderServiceImpl中的如下代码注释掉:

同样在OrderServiceImpl中,写入如下代码,用于设置参数:

完整的订单支付代码如下:

/**
* 订单支付
* @param ordersPaymentDTO
* @return
*/public OrderPaymentVO payment(OrdersPaymentDTO ordersPaymentDTO) throws Exception {// 当前登录用户idLong userId = BaseContext.getCurrentId();User user = userMapper.getById(userId);
/*        //调用微信支付接口,生成预支付交易单JSONObject jsonObject = weChatPayUtil.pay(ordersPaymentDTO.getOrderNumber(), //商户订单号new BigDecimal(0.01), //支付金额,单位 元"苍穹外卖订单", //商品描述user.getOpenid() //微信用户的openid);if (jsonObject.getString("code") != null && jsonObject.getString("code").equals("ORDERPAID")) {throw new OrderBusinessException("该订单已支付");}
*/JSONObject jsonObject = new JSONObject();jsonObject.put("code","ORDERPAID");OrderPaymentVO vo = jsonObject.toJavaObject(OrderPaymentVO.class);vo.setPackageStr(jsonObject.getString("package"));Integer OrderPaidStatus = Orders.PAID;//支付状态,已支付Integer OrderStatus = Orders.TO_BE_CONFIRMED;  //订单状态,待接单LocalDateTime check_out_time = LocalDateTime.now();//更新支付时间orderMapper.updateStatus(OrderStatus, OrderPaidStatus, check_out_time, this.orders.getId());return vo;}

 在OrderMapper中写入如下代码:

@Update("update orders set status = #{orderStatus},pay_status = #{orderPaidStatus} ,checkout_time = #{check_out_time} where id = #{id}")
void updateStatus(Integer orderStatus, Integer orderPaidStatus, LocalDateTime check_out_time, Long id);

现在还有一个问题就是orderId怎么获取?方法如下:

在OrderServiceImpl里加一个全局变量orders(如下图),在submitOrder方法(提交订单)中的如下位置给全局变量赋值:

 

简单解释下,在如下页面点击去支付后就会调用submitOrder方法,将订单数据写入数据库,所以可以在submitOrder方法中获取订单的id。

下面是我刚刚下的一单,status==2代表待派送,pay_status==1代表已支付,测试没问题。 

管理端显示也没问题:

【看到这里如果觉得有帮助点个赞吧👍据说点赞的uu都会收获好运哦!】

Day09 代码导入

这部分在视频中没有,需要参考所给资料的Day09将代码导入到对应文件中。

需要导入如下(注意区分用户端和商家端):

1.查询历史订单(用户端);2.查询订单详情(用户端);3.取消订单(用户端);4.再来一单(用户端)。

1.订单搜索(商家端);2.各个状态的订单数量统计(商家端)。3.查询订单详情(商家端)。4.接单(商家端);5.拒单(商家端);6.取消订单(商家端);7.派送订单(商家端);8.完成订单(商家端)

校验收货地址是否超出配送范围可以选择导入,对后面项目无影响。

在OrderMapper.xml中可能缺少一个update,我补充在下面,需要的话自取

<update id="update" parameterType="Orders">update orders<set><if test="number != null"> number=#{number}, </if><if test="status != null"> status=#{status}, </if><if test="addressBookId != null"> address_book_id=#{addressBookId}, </if><if test="orderTime != null"> order_time=#{orderTime},</if><if test="checkoutTime != null"> checkout_time=#{checkoutTime}, </if><if test="payMethod != null"> pay_method=#{payMethod}, </if><if test="payStatus != null"> pay_status=#{payStatus}, </if><if test="amount != null"> amount=#{amount}, </if><if test="remark != null"> remark=#{remark}, </if><if test="phone != null"> phone=#{phone}, </if><if test="address != null"> address=#{address}, </if><if test="userName != null"> user_name=#{userName}, </if><if test="consignee != null"> consignee=#{consignee} ,</if><if test="cancelReason != null"> cancel_reason=#{cancelReason}, </if><if test="rejectionReason != null"> rejection_reason=#{rejectionReason}, </if><if test="cancelTime != null"> cancel_time=#{cancelTime}, </if><if test="estimatedDeliveryTime != null"> estimated_delivery_time=#{estimatedDeliveryTime}, </if><if test="deliveryStatus != null"> delivery_status=#{deliveryStatus}, </if><if test="deliveryTime != null"> delivery_Time=#{deliveryTime}, </if><if test="packAmount != null"> pack_amount=#{packAmount},</if><if test="tablewareNumber != null"> tableware_number=#{tablewareNumber}, </if><if test="tablewareStatus != null"> tableware_status=#{tablewareStatus}, </if></set>where id=#{id}</update>

用户端项目测试(微信小程序助手):

1.查询历史订单

点击左上角个人中心,然后点击历史订单。

2.查询订单详情

点击历史订单中的订单项。

3.取消订单

下完一单,不支付,到订单详情界面,取消订单。

4.再来一单

点击再来一单,该单所有菜品重新加入购物车。

管理端项目测试(电脑界面):

事先说明一下,虽然暂时无法实现支付功能,但可以人为去修改订单的状态,一共有6种状态,如下图:

可以通过修改数据库的staus字段进行更改:

显示到网页端如下图:

=

1.订单搜索

根据订单号、手机号、下单时间进行查找,发现没问题。

2.各个状态的订单数量统计

看不同状态订单的数量是不是正确的,我这里正常:

3.查看订单详情

点击查看可以看到订单详情

4.接单

对于待接单的项可以点击接单,

然后接单会变成派送,这里没问题:

5.拒单

只有已支付且尚未派送的订单可以拒单,status==2。

6.取消订单

点击取消按钮后,选择取消原因,订单状态变成已取消,取消按钮消失。

7.派送订单

对于待派送的订单可以点击派送:

然后派送按钮会变成完成,状态变成派送中

8.完成订单

点击完成,然后订单会进入已完成:

【如果有需要现成代码欢迎点赞+收藏后私信我】

这篇关于《苍穹外卖》电商实战项目实操笔记系列(P66~P122)【中】的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/721805

相关文章

网页解析 lxml 库--实战

lxml库使用流程 lxml 是 Python 的第三方解析库,完全使用 Python 语言编写,它对 XPath表达式提供了良好的支 持,因此能够了高效地解析 HTML/XML 文档。本节讲解如何通过 lxml 库解析 HTML 文档。 pip install lxml lxm| 库提供了一个 etree 模块,该模块专门用来解析 HTML/XML 文档,下面来介绍一下 lxml 库

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

如何用Docker运行Django项目

本章教程,介绍如何用Docker创建一个Django,并运行能够访问。 一、拉取镜像 这里我们使用python3.11版本的docker镜像 docker pull python:3.11 二、运行容器 这里我们将容器内部的8080端口,映射到宿主机的80端口上。 docker run -itd --name python311 -p

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount

科研绘图系列:R语言扩展物种堆积图(Extended Stacked Barplot)

介绍 R语言的扩展物种堆积图是一种数据可视化工具,它不仅展示了物种的堆积结果,还整合了不同样本分组之间的差异性分析结果。这种图形表示方法能够直观地比较不同物种在各个分组中的显著性差异,为研究者提供了一种有效的数据解读方式。 加载R包 knitr::opts_chunk$set(warning = F, message = F)library(tidyverse)library(phyl

在cscode中通过maven创建java项目

在cscode中创建java项目 可以通过博客完成maven的导入 建立maven项目 使用快捷键 Ctrl + Shift + P 建立一个 Maven 项目 1 Ctrl + Shift + P 打开输入框2 输入 "> java create"3 选择 maven4 选择 No Archetype5 输入 域名6 输入项目名称7 建立一个文件目录存放项目,文件名一般为项目名8 确定

【生成模型系列(初级)】嵌入(Embedding)方程——自然语言处理的数学灵魂【通俗理解】

【通俗理解】嵌入(Embedding)方程——自然语言处理的数学灵魂 关键词提炼 #嵌入方程 #自然语言处理 #词向量 #机器学习 #神经网络 #向量空间模型 #Siri #Google翻译 #AlexNet 第一节:嵌入方程的类比与核心概念【尽可能通俗】 嵌入方程可以被看作是自然语言处理中的“翻译机”,它将文本中的单词或短语转换成计算机能够理解的数学形式,即向量。 正如翻译机将一种语言

【学习笔记】 陈强-机器学习-Python-Ch15 人工神经网络(1)sklearn

系列文章目录 监督学习:参数方法 【学习笔记】 陈强-机器学习-Python-Ch4 线性回归 【学习笔记】 陈强-机器学习-Python-Ch5 逻辑回归 【课后题练习】 陈强-机器学习-Python-Ch5 逻辑回归(SAheart.csv) 【学习笔记】 陈强-机器学习-Python-Ch6 多项逻辑回归 【学习笔记 及 课后题练习】 陈强-机器学习-Python-Ch7 判别分析 【学