Spring Boot企业微信点餐系统学习心得和学习笔记

Spring Boot企业微信点餐系统学习心得

项目地址

https://github.com/coder-zrl/sell/

创新之处

  • 个人使用mybatis作为数据库,并且手写了一个Page类
  • 登陆验证跳转和下单实时提醒使用了websocket推送
  • 商家的后台管理系统很全面
  • 做了详细的异常处理
  • 创建了许多枚举类型

不足之处

  • 在设计websocket的时候应该指定返回特殊代码代表不同含义,这个项目登录的时候订单列表页会提醒

  • 前端有些潦草

  • 用户点餐界面还没做

  • 用户查询订单还没做,但是api已经实现了

  • session没仔细搞,readis没仔细搞,分布式没仔细搞,因为时间不够了,要复习啦

  • 没创建搜索框

Java基础补充

  • for-each语法

    for (int data: res) {
        System.out.println(data);
    }
    
  • 创建List,List只是一个接口,不能被实例化

    List<int> intList = new ArrayList<>();
    
  • 创建一个异常类

    public class SellException extends RuntimeException{
        private Integer code;
    
        public SellException(ResultEnum resultEnum) {
            super(resultEnum.getMessage());
            this.code = resultEnum.getCode();
        }
    }
    
  • 只要不是同一个函数,就能相互调用,甚至重载函数直接return另一个形式

  • 新建一个BigDecimal必须传入数值哦,可以写new BigDecimal(0);或者new BigDecimal(BigInteger.ZERO);

  • 精确的数字BigDecimal做加减乘除都有自己的函数

  • 生成id要加上synchronized关键字,防止多线程导致错位

  • CollectionUtils.isEmpty(orderDetailList)判断数组是否为空

注解补充

  • @Autowired自动注入。测试的时候别忘了对Service的impl加上自动注入,直接new一下是得不到的

  • @RestController表示这个类是一个Controller,并且可以接收json数据

  • @RequestMapping修饰Controller类的时候,作用是先创建一个大的路由作为url前缀

  • @Mapper表示这是一个mapper,即数据库增删改查

  • @Slf4j安装了lombok插件同时导入了依赖,就不用写logger对象了,直接使用log.info之类的方法

  • @DynamicUpdate可以实时更新数据库时间,前提是你设置了自动设置时间

  • @Configuration表示这个类是起配置作用的

  • @Bean

  • @JsonProperty(“name”)对VO修饰,表示这个属性名称传到前端是name

  • @Transactional是给函数加上事务回滚的功能

  • @Transient注解会在自动匹配字段的时候忽略掉这个属性

  • @Validated OrderForm orderForm,BindingResult bindingResult可以使用OrderForm这个类传递参数,也可以写成RequestBody

  • @JsonSerialize(using=xxx.class)可以直接把某个属性转化为想要的类型,例如

    https://blog.csdn.net/weixin_40388298/article/details/88634100

  • @JsonInclude(JsonInclude.Include.NON_NULL),这个类返回给前端的json数据不包含null字段,全局配置直接去配置文件里写jackson de…-pro…-inclusion: not_null

  • @RunWith(SpringRunner.class)
    @SprintBootTest
    测试类名
        @Test
        测试方法
    

Spring补充

  • Controller方法中参数添加@RequestBody()使用一个类来接收json数据,就不用一点点写@RequestParams了,注意字段要与json一致

  • 单元测试的时候,直接右键Go to->Test

  • 层级任务

    config--配置文件类
    controller--对数据的展现,逻辑在service层写,不要写在controller层
    service--放置主要逻辑的地方,Seivice层是给Controller层调用的
    impl--是service子文件夹,实现service定义的函数
    mapper(dao)--对数据库增删改查
    pojo--实体类
    VO--视图管理器,传数据给前端
    DTO--是数据传输对象
    utils--工具类
    enums--放置枚举类型
    exception--放置错误类型
    converiter--用于数据转化创建的类
    
  • 学到了一个新的东西,视图管理器VO,创建一个类,然后声明属性,返回这个类,就构成了json格式

  • @JsonProperty(“name”)注解,将字符序列化,即传到前端的json字段是name,而不是类中的categoryName属性

  • VO可以嵌套,就类似json格式数据嵌套,直接把一个VO丢到另一个VO中就欧克

    @RestController  //可以接收json
    @RequestMapping("/buyer/product")  //url前缀
    public class BuyerProductController {
        @GetMapping("/list")  //访问http://localhost:8080/sell/buyer/product/list
        public ResultVO list(){
            ResultVO resultVO = new ResultVO();
            ProductVO productVO = new ProductVO();
            ProductInfoVO productInfoVO = new ProductInfoVO();
    
            productVO.setProductInfoVOList(Arrays.asList(productInfoVO));
            resultVO.setData(Arrays.asList(productVO));
            resultVO.setCode(0);
            resultVO.setMeg("成功");
            
            return resultVO;
        }
    }
    
  • impl实现Service的接口的时候要加上@Service注解,否则提示找不到Bean

Mybatis补充

  • Mybatis一定一定是写无参构造方法!!!即不写构造方法

  • 记得写@Mapper注解

  • 开启数据库命名规范与Java命名规范有两种形式,写配置类或者改配置文件

    mybatis:
      configuration:
        map-underscore-to-camel-case: true
    
  • 设置id自增,在传数据的时候就不用把id传进去了

  • mapper的sql必须是函数里的参数,否则找不到

  • 前端传过来商品id和数量,你去数据库查相关商品信息,然后写入订单情况,这是数据库关联的

其他工具

  • Gson可以很方便的将字符串与json转换(写个对象去接收json数据)

    <dependency>
        <groupId>com.google.code.gson</groupId>
        <artifactId>gson</artifactId>
    </dependency>
    
  • lombok插件和依赖,可以加上@Data注解不写getter、setter、tostring还有配置logback不用实例化logger

    <dependence>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependence>
    
  • BeanUtils.copyProperties(productInfo,productInfoVO);可以复制属性,但是只有同名的属性才能复制,把前面的复制到后面的去。

  • postman 使用get方法的时候要在Params上加参数,Post方法在Body加参数

相关依赖

<!--        lombok-->
		<dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
<!--        Gson-->
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
        </dependency>
<!--        数据库依赖-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.3</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
<!--        swagger-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.7.0</version>
        </dependency>
<!--        测试用的-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-test</artifactId>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
        </dependency>

配置文件

有时候配置文件不对也会报错,例如中文全变成了"?",例如时区有问题,遇到问题不要慌,先百度一下

application.yml

spring:
  datasource:
    username: root
    password: 422518
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/sell?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8&useSSL=true

mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--给类起别名-->
    <typeAliases>
        <typeAlias type="com.example.demo.pojo.Veg" alias="Veg"/>
    </typeAliases>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/sell?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>

</configuration>

Mybatis一定一定不要写有参构造方法!!!

Mybatis一定一定要写无参构造方法!!!

第四章:买家端

在这里插入图片描述

收获

  • mapper的sql一定要是函数里的参数

    //对的
    @Update("update product_category set category_name=#{id} where category_id=#{id}")
    public int updateProductNameById(int id,String name);
    //错的,应该是#{id}和#{type}。这么写是取出对象的属性
    @Update("update product_category set category_type=#{category_type} where category_id=#{categoryId}")
    public int updateProductTypeById(int id,String type);
    
  • lombok插件和依赖。可以加上@Data注解不写getter、setter、tostring还有配置logback不用实例化logger

    <dependence>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependence>
    
  • 给pojo,也就是对象加上@DynamicUpdate注解可以实时更新时间(前提是你设置了创建自动生成时间)

  • 使用mysql可以找到包含所有类型的数据或者直接find一个值,需要在pojo加@Entity //数据库映射成对象

    在这里插入图片描述

数据库相关依赖

pom.xml

<!--        数据库依赖-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

配置mysql

application.yml

spring:
  datasource:
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/sell?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8&useSSL=true

第五章:商品

收获

  • 测试mapper不用写controller,可以使用Test,直接对函数进行传值

  • for-each语法

    for (int data: res) {
        System.out.println(data);
    }
    
  • Mapper忘了加@Mapper注解了

  • 为了接收json格式要在Controller类中加上@RestController注解

  • 可以直接对Controller对象加上@RequestMapping注解作为url前缀

  • 学到了一个新的东西,视图管理器VO,创建一个类,返回这个类,就构成了json格式

  • @JsonProperty(“name”)注解,将字符序列化,即传到前端的json字段是name,而不是类中的categoryName属性

  • VO可以嵌套,就类似json格式数据嵌套,直接把一个VO丢到另一个VO中就欧克

    @RestController  //可以接收json
    @RequestMapping("/buyer/product")  //url前缀
    public class BuyerProductController {
        @GetMapping("/list")  //访问http://localhost:8080/sell/buyer/product/list
        public ResultVO list(){
            ResultVO resultVO = new ResultVO();
            ProductVO productVO = new ProductVO();
            ProductInfoVO productInfoVO = new ProductInfoVO();
    
            productVO.setProductInfoVOList(Arrays.asList(productInfoVO));
            resultVO.setData(Arrays.asList(productVO));
            resultVO.setCode(0);
            resultVO.setMeg("成功");
            
            return resultVO;
        }
    }
    
  • Arrays.asList就可以,但是ArrayList就不可以

  • 为什么List<Integer> categoryTypeList = new ArrayList<>();可以,但是List<int> categoryTypeList = new ArrayList<>();不可以

  • List<Integer> categoryTypeList = new ArrayList<>();

  • Bean工具包怕,BeanUtils.copyProperties(productInfo,productInfoVO);可以复制属性,但是只有同名的才能复制,把前面的复制到后面的去。!!!但是有个坑,如果写在最后了,有时候会有问题,比如你先给某个属性赋值了,但是使用了这个方法,可能会把原来的值设置为null!!!

  • Mapper的实体类没有写无参构造方法,Mybatis会报错

  • impl实现Service的接口的时候要加上@Service注解,否则提示找不到Bean

  • 只要不是同一个函数,就能相互调用,甚至重载函数直接return另一个形式

第六章:订单

收获

  • DAO层(Mapper)就是数据库的增删改查
  • Controller是对数据的展现
  • Service是放置主要逻辑的地方
  • DTO是数据传输对象
  • Seivice层是给Controller层调用的
  • 加上@Transient注解会在自动匹配字段的时候忽略掉
  • 异常单独使用一个类
  • 新建一个BigDecimal必须传入数值哦,可以写new BigDecimal(0);或者new BigDecimal(BigInteger.ZERO);
  • 生成id要加上synchronized关键字,防止多线程导致错位
  • @Transactional是给函数加上事务回滚的功能
  • 测试的时候别忘了对Service的实现加上自动注入,直接new一下是得不到的
  • 前端传过来商品id和数量,你去数据库查相关商品信息,然后写入订单情况,这是数据库关联的
  • CollectionUtils.isEmpty(orderDetailList)判断数组是否为空
  • OrderMasterMapper的查询为null ,这是因为类的构造方法存在两个,一个是空的,一个是有参的,在sql中查询返回结果是这个类对象,无法自动注入值,并且这个注入值是按表的顺序和你参数的顺序一一对应的,可以写非全参的,目前我无法拯救
  • 前端传过来的对象可以使用String接收,使用gson依赖转换json格式
  • @Validated OrderForm orderForm,BindingResult bindingResult可以使用OrderForm这个类传递参数,也可以写成RequestBody
  • 在一个类中的属性加上注解@JsonSerialize(using=xxx.class)可以直接把数据转化为想要的类型
  • postman 使用get方法的时候要在Params上加参数,Post方法在Body加参数
  • 想在一个结果里面不返回为null字段,在那个类的头部加上@JsonInclude(JsonInclude.Include.NON_NULL)
  • 如果要在很多类里面加这个注解的话会很麻烦,我们直接去配置文件jackson de…-pro…-inclusion: not_null
  • 在VO层给属性加上@JsonProperty(“name”) //这样将数据传到前端,字段就变成了name
  • 把逻辑放在service层去做,不要写在Controller层
  • @Transactional表示事务回滚,在impl层的方法加上这个会很好

第七章:微信授权

https://www.bilibili.com/video/BV18E411x7ne

有了微信授权才能获取openid,有了openid才能进行后面的操作,对于某个小程序或者公众号,用户的openid是唯一的

获取openid的两种方式

  • 手工方式
  • 利用第三方SDK

第一步:进入官方文档,申请接口测试号

https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Requesting_an_API_Test_Account.html
  • appID是公众号的唯一标识符

  • appsecret是你的密钥

扫描测试号二维码–>找到网页服务–>网页账号–>网页授权获取用户基本信息

https://natapp.cn/ 购买隧道,推荐VIP_1型,然后注册二级域名,填写到接口测试号的网页授权获取用户基本信息中,我申请的是coder-zrl.nat100.top 2021.12.10到期,配置隧道的二级域名和端口号(8080),下载程序,命令行执行natapp.exe -authtoken=259ca74374ba5aaa

官方文档–>微信网页开发–>网页授权

复制提供的引导链接,写上自己的appid和redirect_uri以及scope

https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx6640a7f5b82ebd37&redirect_uri=http://coder-zrl.nat100.top/sell/weixin/auth&response_type=code&scope=snsapi_base&state=123#wechat_redirect

第二步:获取用户code

写一个controller

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
@RequestMapping("/weixin")
public class WeixinController {
    @GetMapping("/auth")
    public void auth(@RequestParam("code") String code) {
        log.info("进入了auth方法");
        log.info("【获取code】code={}",code);
    }
}

第三步:通过code获取access_token

url的参数官方文档都有详细说明

https://api.weixin.qq.com/sns/oauth2/access_token?appid=wx6640a7f5b82ebd37&secret=85cf924b23a717e12caf329150b988c9&code=CODE&grant_type=authorization_code

返回值是json数据,里面包含openid,具体每个变量什么意义官方文档也有说明

{
  "access_token":"ACCESS_TOKEN",
  "expires_in":7200,
  "refresh_token":"REFRESH_TOKEN",
  "openid":"OPENID",
  "scope":"SCOPE" 
}

使用Spring的模板进行url访问,就是在上面的controller再加上下面的代码

我写了一个WeixinDTO和上面的字段一致,用来获取openid

import com.example.sell.dto.WeixinDTO;
import com.google.gson.Gson;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

/**
 * 微信端的调试
 * @author ZRL
 * @date 2020/12/10 1:25
 */
@Slf4j
@RestController
@RequestMapping("/weixin")
public class WeixinController {
    @GetMapping("/auth")
    public void auth(@RequestParam("code") String code) {
        log.info("进入了auth方法");
        log.info("【获取code】code={}", code);
        String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=APPSECRET&code=" + code + "&grant_type=authorization_code";
        //Spring提供了一个简单便捷的模板类实现java代码访问restful服务
        //get是指调用get方法,object是我们要转化成的类型
        RestTemplate restTemplate = new RestTemplate();
        String response = restTemplate.getForObject(url, String.class);
        log.info("response={}",response);
        Gson gson = new Gson();
        WeixinDTO data = gson.fromJson(response,WeixinDTO.class);
        log.info("openid={}",data.getOpenid());
    }
}

第八章:微信支付与退款

https://www.bilibili.com/video/BV1pD4y1d7jd

第九章:买家订单

收获

  • thymeleaf语法

  • ibootstrap进行拖拽构建网页结构

  • css放在link,可以不用下载到本地

  • 在我们将数据展示的时候,orderDto的支付状态是代码,我们想变为文字,不应在html里写表达式判断,应该是给类重新创建个方法获取枚举的值

  • 然后就会有问题,好几种状态,难道每个类都要写重复代码吗?其实不需要,我们只需要将枚举继承自同一个接口,接口声明一个方法,然后在Util包中实现它即可

  • 实例化泛型的时候要对他说明,继承自哪里

    public static <T extends CodeEnum>T getByCode(Integer code,Class<T> enumClass) 
    
  • 给属性或者方法添加@JsonIgnore可以在返回json对象的时候忽略这个字段

添加依赖

使用到了thymeleaf模板

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

开始搭建界面

使用ibootstrap进行拖拽,生成前端代码,然后copy官网的对应版本的bootstrap.min.css链接到head中的link链接中,也可以直接复制link标签,奥,原来css可以不用下载啊!真是舒服!!!但是可能会导致范围跟速度很慢呢,而且必须有网络

第十章:商品上下架

收获

  • 当html静态资源改变的时候,我们只需要点一下那个锤子就可以了,不需要重启
  • 侧边栏效果:common/nav.html—css/style.css,然后再给html文件的head部分添加<link rel="stylesheet" href="/sell/css/style.css">,然后再修改一下网页结构
  • 写网页的时候,尽量使用thymeleaf的语法,因为如果设置了网页url前缀,thymeleaf可以自动拼接,但是原网页还需要加上

第十一章:卖家商品

收获

  • 当参数不是必填的时候可以这样写

    @RequestParam(value = "productId",required = false)
    
  • 新增和编辑可以使用一套模板

第十二章:分布式与websocket

收获

  • websocket分客户端和服务端,客户端写在js里面
  • 了解了分布式的一些概念,了解了cookie和token的机制,学到了AOP开发

websocket

<script>
    var websocket=null;
    //判断浏览器是否支持
    if('Websocket' in window) {
        websocket = new WebSocket('ws://localhost:8080/sell/websocket');
    } else {
        alert('该浏览器不支持websocket');
    }
    websocket.onopen = function (event) {
        console.log('建立连接');
    }
    websocket.onclose = function (event) {
        console.log('连接关闭');
    }
    websocket.onmessage = function (event) {
        console.log('收到消息:'+event.data);
        //弹窗提醒,播放音乐
    }
    websocket.onerror = function (event) {
        alert('webcoket通信发生错误');
    }
    websocket.onbeforeload = function () {
        websocket.close();
    }
</script>

依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

写一个配置类

@Component
public class WebSocketConfig {
    
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

java

@Component
@ServerEndpoint("/websocket")
@Slf4j
public class Websocket {
    private Session session;
    //    存储websocket
    private static CopyOnWriteArraySet<Websocket> webSocketSet = new CopyOnWriteArraySet<>();
    @OnOpen
    public void onOpen(Session session) {
        this.session = session;
        webSocketSet.add(this);
        log.info("有新的客户端连接,当前总数为:{}",webSocketSet.size());
    }
    @OnClose
    public void onClose() {
        webSocketSet.remove(this);
        log.info("客户端关闭,当前总数为:{}",webSocketSet.size());
    }
    @OnMessage
    public void onMessage(String message) {
        log.info("【收到消息:{}】",message);
    }
    public void sendMessage(String message) {
        for(Websocket websocket:webSocketSet) {
            log.info("【websocket广播消息,message={}】",message);
            try {
                websocket.session.getBasicRemote().sendText(message);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

然后自动注入到别的impl中

第十三章:压测

创建一个handler文件夹,里面放处理异常的handler

  • @ExceptionHandler(value=xxx.class)注解来接收处理哪类异常,当运行过程中报错了就会自动执行里面的代码
  • 加上@ResponseStatus注解后可以返回对应的状态码
  • 压测工具Apache ab
  • 使用synchronized虽然可以解决线程冲突,但是就变成单线程了很慢,学习一下radis分布式锁

第十四章:项目部署

  • 打包成jar包的格式(也可以放在toncat上),在pom可以指定文件名,直接使用maven的Lifecycle下的package打包成jar包的格式就可以打包为jar包
  • 使用java -jar name可以运行
  • 使用jar包运行的时候可以指定端口和运行环境
  • 使用scp命令传输文件
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

笼中小夜莺

嘿嘿嘿,请用金钱尽情地蹂躏我吧

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值