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&useUnicode=true&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命令传输文件