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

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

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

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

 一、微信小程序篇

1.1 本章介绍 P66

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

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

1.2 (HttpClient)介绍 P67

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

maven坐标:


        org.apache.httpcomponents
        httpclient
        4.5.13

《苍穹外卖》电商实战项目实操笔记系列(P66~P122)【中】_第1张图片《苍穹外卖》电商实战项目实操笔记系列(P66~P122)【中】_第2张图片

1.3 (HttpClient)发GET请求 P68

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

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

public class HttpClientTest{
    @Test
    public 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。

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

1.5 (微信小程序)介绍 P70

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

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

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

1.6 (微信小程序)准备 P71

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

小程序 (qq.com)

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

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

《苍穹外卖》电商实战项目实操笔记系列(P66~P122)【中】_第6张图片《苍穹外卖》电商实战项目实操笔记系列(P66~P122)【中】_第7张图片

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

AppID(小程序ID):wx3910b10fd7db38d1

AppSecret(小程序密钥):f274884c2a56466016ebbcf009404e63

《苍穹外卖》电商实战项目实操笔记系列(P66~P122)【中】_第8张图片《苍穹外卖》电商实战项目实操笔记系列(P66~P122)【中】_第9张图片

下载微信开发者工具。

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

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

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

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

《苍穹外卖》电商实战项目实操笔记系列(P66~P122)【中】_第12张图片《苍穹外卖》电商实战项目实操笔记系列(P66~P122)【中】_第13张图片《苍穹外卖》电商实战项目实操笔记系列(P66~P122)【中】_第14张图片

下面是开发界面:

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

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

《苍穹外卖》电商实战项目实操笔记系列(P66~P122)【中】_第16张图片《苍穹外卖》电商实战项目实操笔记系列(P66~P122)【中】_第17张图片

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

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

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

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

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

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

wxml是小程序的页面结构。

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

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

《苍穹外卖》电商实战项目实操笔记系列(P66~P122)【中】_第19张图片​ 《苍穹外卖》电商实战项目实操笔记系列(P66~P122)【中】_第20张图片

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

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

index.wxml中的代码如下:


  
    {{msg}}
  
  
    
    昵称: {{nickName}}
    
  

这个文件主要是负责定义前端展示的元素,比如{{}}是文本内插的符号,按钮用 昵称: {{nickName}} 授权码:{{code}}

 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发送查询,返回店铺状态。

《苍穹外卖》电商实战项目实操笔记系列(P66~P122)【中】_第24张图片《苍穹外卖》电商实战项目实操笔记系列(P66~P122)【中】_第25张图片

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完整代码如下: 


  
    {{msg}}
  
  
    
    昵称: {{nickName}}
    
  
  
    
    授权码:{{code}}
  
  
    
  

1.11 (微信小程序)发布 P76

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

点击上传按钮:

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

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

《苍穹外卖》电商实战项目实操笔记系列(P66~P122)【中】_第27张图片《苍穹外卖》电商实战项目实操笔记系列(P66~P122)【中】_第28张图片

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

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

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

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

《苍穹外卖》电商实战项目实操笔记系列(P66~P122)【中】_第30张图片《苍穹外卖》电商实战项目实操笔记系列(P66~P122)【中】_第31张图片

这里要确保AppID是自己的:

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

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

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

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

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

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

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

下面是请求的参数:

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

使用PostMan进行测试,成功:

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

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

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

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

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

数据库设计如下:

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

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

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

sky:
  wechat:
    appid: wx3910b10fd7db38d1
    secret: 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 {
    @Autowired
    private UserService userService;
    @Autowired
    private JwtProperties jwtProperties;
    //微信登录
    @PostMapping("/login")
    @ApiOperation("微信登录")
    public Result login(@RequestBody UserLoginDTO userLoginDTO){
        log.info("微信用户登录;{}",userLoginDTO.getCode());
        //微信登录
        User user = userService.wxLogin(userLoginDTO);
        //为微信用户生成jwt令牌
        Map 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";
    @Autowired
    private WeChatProperties weChatProperties; //配置文件中传入的值会被放入配置类,所以只需要自动注入然后获取即可
    @Autowired
    private 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){
        //调用微信接口服务,来获得当前微信用户的openid
        Map 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的具体代码:




    
        insert into user(openid,name,phone,sex,id_number,avatar,create_time)
        values (#{openid},#{name},#{phone},#{sex},#{idNumber},#{avatar},#{createTime})
    

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

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

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

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

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

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

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

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

@Component
@Slf4j
public class JwtTokenUserInterceptor implements HandlerInterceptor {
    @Autowired
    private JwtProperties jwtProperties;
    //校验jwt
    public 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查询包含的菜品。

《苍穹外卖》电商实战项目实操笔记系列(P66~P122)【中】_第39张图片《苍穹外卖》电商实战项目实操笔记系列(P66~P122)【中】_第40张图片《苍穹外卖》电商实战项目实操笔记系列(P66~P122)【中】_第41张图片《苍穹外卖》电商实战项目实操笔记系列(P66~P122)【中】_第42张图片

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

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

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

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

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


        update setmeal
        
             category_Id = #{categoryId},
            name = #{name},
            price = #{price},
            status = #{status},
            description = #{description},
            image = #{image},
        
        where id = #{id}

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

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

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

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

然后在资料包中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编译一下,然后到小程序再编译一下。可以看到小程序中已经出现各类菜品(图片没显示属于正常,下面我详细说),然后会有选规格。

《苍穹外卖》电商实战项目实操笔记系列(P66~P122)【中】_第46张图片《苍穹外卖》电商实战项目实操笔记系列(P66~P122)【中】_第47张图片

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

出现问题:

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

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

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

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

《苍穹外卖》电商实战项目实操笔记系列(P66~P122)【中】_第48张图片​ 《苍穹外卖》电商实战项目实操笔记系列(P66~P122)【中】_第49张图片

二、缓存商品和购物车篇

2.1 本章内容介绍 P86

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

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

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

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

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

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

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

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

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

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

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

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

@RestController("userDishController")
@RequestMapping("/user/dish")
@Slf4j
@Api(tags = "C端-菜品浏览接口")
public class DishController {
    @Autowired
    private DishService dishService;
    @Autowired
    private RedisTemplate redisTemplate;
    // 根据分类id查询菜品
    @GetMapping("/list")
    @ApiOperation("根据分类id查询菜品")
    public Result> list(Long categoryId) {
        //构造redis中的key,规则:dish_分类id
        String key = "dish_" + categoryId;
        //查询redis中是否存在菜品数据
        List list = (List)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缓存。

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

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。

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

2.7 SpringCache入门 P92

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

《苍穹外卖》电商实战项目实操笔记系列(P66~P122)【中】_第54张图片​ 《苍穹外卖》电商实战项目实操笔记系列(P66~P122)【中】_第55张图片

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

《苍穹外卖》电商实战项目实操笔记系列(P66~P122)【中】_第56张图片《苍穹外卖》电商实战项目实操笔记系列(P66~P122)【中】_第57张图片

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


    org.springframework.boot
    spring-boot-starter-cache


    org.springframework.boot
    spring-boot-starter-data-redis

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方法发送请求:

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

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

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

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

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

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包下导入下面两个坐标:


    org.springframework.boot
    spring-boot-starter-data-redis


    org.springframework.boot
    spring-boot-starter-cache

然后要在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会把数据缓存。

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

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

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

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

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

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

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

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

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

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

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

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

@RestController
@RequestMapping("/user/shoppingCart")
@Slf4j
@Api(tags="C端购物车相关接口")
public class ShoppingCartController {
    @Autowired
    private 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 {
    @Override
    public void addShoppingCart(ShoppingCartDTO shoppingCartDTO) {
        //判断当前加入购物车中的商品是否已经存在了
        //如果已经存在,只需要数量+1
        //如果不存在,则需要插入一条购物车数据
    }
}

 更多细节放在下节完善。

2.15(添购物车)开发 P100

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

@Service
@Slf4j
public class ShoppingCartServiceImpl implements ShoppingCartService {
    @Autowired
    private ShoppingCartMapper shoppingCartMapper;
    @Override
    public void addShoppingCart(ShoppingCartDTO shoppingCartDTO) {
        //判断当前加入购物车中的商品是否已经存在了
        ShoppingCart shoppingCart = new ShoppingCart();
        BeanUtils.copyProperties(shoppingCartDTO,shoppingCart);
        //userid暂时还不知道,从ThreadLocal取
        Long userId = BaseContext.getCurrentId();
        shoppingCart.setUserId(userId);
        List list = shoppingCartMapper.list(shoppingCart);
        //如果已经存在,只需要数量+1
        if(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 list(ShoppingCart shoppingCart);
    //根据id修改商品数量
    @Update("update shopping_cart set number = #{number} where id = #{id}")
    void updateNumberById(ShoppingCart shoppingCart);
}

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





    

2.16(添购物车)开发 P101

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

@Service
@Slf4j
public class ShoppingCartServiceImpl implements ShoppingCartService {
    @Autowired
    private ShoppingCartMapper shoppingCartMapper;
    @Autowired
    private DishMapper dishMapper;
    @Autowired
    private SetmealMapper setmealMapper;
    @Override
    public void addShoppingCart(ShoppingCartDTO shoppingCartDTO) {
        //判断当前加入购物车中的商品是否已经存在了
        ShoppingCart shoppingCart = new ShoppingCart();
        BeanUtils.copyProperties(shoppingCartDTO,shoppingCart);
        //userid暂时还不知道,从ThreadLocal取
        Long userId = BaseContext.getCurrentId();
        shoppingCart.setUserId(userId);
        List list = shoppingCartMapper.list(shoppingCart);
        //如果已经存在,只需要数量+1
        if(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为空的根源在于拦截器的问题。

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

拦截器如果有问题可以参考我前面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(){
    List list = shoppingCartService.showShoppingCart();
    return Result.success(list);
}

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

List showShoppingCart();

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

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

大致的效果如下图:

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

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包下,其它很简单。

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

3.4 (地址簿)功能测试 P108

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

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

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

接口设计:

《苍穹外卖》电商实战项目实操笔记系列(P66~P122)【中】_第68张图片《苍穹外卖》电商实战项目实操笔记系列(P66~P122)【中】_第69张图片

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

数据库设计无需多言。

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

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

@RestController("userOrderController")
@RequestMapping("/user/order")
@Api(tags="用户端订单相关接口")
@Slf4j
public class OrderController {
    @Autowired
    private OrderService orderService;
    @PostMapping("/submit")
    @ApiOperation("用户下单")
    public Result 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 {
    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private 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 {
    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private OrderDetailMapper orderDetailMapper;
    @Autowired
    private AddressBookMapper addressBookMapper;
    @Autowired
    private 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 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 {
    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private OrderDetailMapper orderDetailMapper;
    @Autowired
    private AddressBookMapper addressBookMapper;
    @Autowired
    private 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 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,写入如下代码:




    
        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})
    

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

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

下面是OrderService的最终代码:

@Service
public class OrderServiceImpl implements OrderService {
    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private OrderDetailMapper orderDetailMapper;
    @Autowired
    private AddressBookMapper addressBookMapper;
    @Autowired
    private ShoppingCartMapper shoppingCartMapper;
    @Transactional
    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 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 orderDetailList = new ArrayList<>();
        //3.向订单明细表插入n条数据
        for(ShoppingCart cart: shoppingCartList){
            OrderDetail orderDetail = new OrderDetail();//订单明细
            BeanUtils.copyProperties(cart,orderDetail);
            orderDetail.setOrderId(orders.getId());//设置当前订单明细关联的订单id
            orderDetailList.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 orderDetailList);
}

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




    
        insert into order_detail(name,image,order_id,dish_id,setmeal_id,
                                 dish_flavor,number,amount)
        values
        
            (#{od.name},#{od.image},#{od.orderId},#{od.dishId},
             #{od.setmealId},#{od.dishFlavor},#{od.number},#{od.amount})
        
    

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

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

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

《苍穹外卖》电商实战项目实操笔记系列(P66~P122)【中】_第70张图片《苍穹外卖》电商实战项目实操笔记系列(P66~P122)【中】_第71张图片

orders表里会有相关的信息:

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

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

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

3.12(订单支付)介绍P116

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

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

3.13(订单支付)介绍P117

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

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

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

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

3.14(订单支付)介绍P118

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

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

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

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

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

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

获取临时域名

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

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

在下面的地址栏输入cmd:

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

到官网获取隧道Authtoken:

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

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

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

会生成一个配置文件:

输入下面的代码:

cpolar.exe http 8080

获得临时的域名:

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

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

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

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}

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

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

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

sky:
  wechat:
    appid: wx3910b10fd7db38d1
    secret: f274884c2a56466016ebbcf009404e63
    mchid: 1561414331
    mchSerialNo: 4B3B3DC35414AD50B1B755BAF8DE9CC7CF407606
    privateKeyFilePath: C:\software\apiclient_key.pem
    apiV3Key: CZBK51236435wxpay435434323FFDuv3
    weChatPayCertFilePat: C:\software\wechatpay_166D96F876F45C7D07CE98952A96EC980368ACFC.pem
    notifyUrl: https://9ea0754.r19.cpolar.top/notify/paySuccess
    refundNotifyUrl: 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 orders
        
             number=#{number}, 
             status=#{status}, 
             address_book_id=#{addressBookId}, 
             order_time=#{orderTime},
             checkout_time=#{checkoutTime}, 
             pay_method=#{payMethod}, 
             pay_status=#{payStatus}, 
             amount=#{amount}, 
             remark=#{remark}, 
             phone=#{phone}, 
             address=#{address}, 
             user_name=#{userName}, 
             consignee=#{consignee} ,
             cancel_reason=#{cancelReason}, 
             rejection_reason=#{rejectionReason}, 
             cancel_time=#{cancelTime}, 
             estimated_delivery_time=#{estimatedDeliveryTime}, 
             delivery_status=#{deliveryStatus}, 
             delivery_Time=#{deliveryTime}, 
             pack_amount=#{packAmount},
             tableware_number=#{tablewareNumber}, 
             tableware_status=#{tablewareStatus}, 
        
        where id=#{id}
    

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

支付成功效果如下:

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

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

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

下面是修改过程:

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

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

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

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

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

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

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

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

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

/**
* 订单支付
* @param ordersPaymentDTO
* @return
*/
 public OrderPaymentVO payment(OrdersPaymentDTO ordersPaymentDTO) throws Exception {
        // 当前登录用户id
        Long 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方法(提交订单)中的如下位置给全局变量赋值:

《苍穹外卖》电商实战项目实操笔记系列(P66~P122)【中】_第88张图片 《苍穹外卖》电商实战项目实操笔记系列(P66~P122)【中】_第89张图片

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

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

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

管理端显示也没问题:

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

Day09 代码导入

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

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

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

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

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

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


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

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

1.查询历史订单

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

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

2.查询订单详情

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

3.取消订单

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

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

4.再来一单

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

《苍穹外卖》电商实战项目实操笔记系列(P66~P122)【中】_第93张图片《苍穹外卖》电商实战项目实操笔记系列(P66~P122)【中】_第94张图片

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

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

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

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

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

显示到网页端如下图:

《苍穹外卖》电商实战项目实操笔记系列(P66~P122)【中】_第97张图片=

1.订单搜索

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

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

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

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

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

3.查看订单详情

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

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

4.接单

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

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

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

5.拒单

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

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

6.取消订单

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

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

7.派送订单

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

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

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

8.完成订单

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

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

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

你可能感兴趣的:(笔记,notepad++,java,springboot,spring,项目)