最近通过慕课网学习了《基于springboot企业微信点餐系统项目》,干货满满,既有springboot知识的学习,又有业务逻辑的处理体会。更多的是接触了企业级开发过程中各种编程心得和技巧,能够很大程度的帮助我们规范开发的流程,使得我们的代码更加优雅和实现可维护性。
git地址:https://gitee.com/jiayuan1234/sell
关于springcloud的可以看参考这篇文章:https://blog.csdn.net/zhang_jiayuan/article/details/83994948
Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置,也就是人们常说的“约定大于配置”。
基于springboot进行开发,最直观的感受就是,它使得我们能在很短的时间内快速搭建一个项目环境,并且能够方便的集成各种优秀组件,通过application.yml文件进行统一配置和管理。同时它也是基于maven构建,以传统的ssm框架为基础,帮助我们更好的开发spring应用程序,很好的解决了先前“配置地狱”的苦恼,使得我们能够更加专注于业务逻辑的开发,提高效率。
jdk:1.8.0_65
mysql:5.7.1
maven:3.3.9
redis: 3.0.6
springboot-version: 1.5.4.RELEASE
IDE: Intellij IDEA 2017.2,navicat 12
本次学习是跟随慕课网上《基于springboot企业微信点餐系统》进行的,以Spring Boot和微信特性为核心技术栈,实现一个从下单到接单流程完整,包含买家端和卖家端前后台功能的微信点餐系统,一步步设计并开发一个中小型企业级Java应用。
通过IDEA创建springboot项目,Spring Initializr帮我们初始化了项目目录,主要包括main(java,resources),test。如下是到com.imooc这一层级,再以下均为自定义
spring-boot-starter-*起步依赖是SpringBoot核心之处,它提供了Spring和相关技术提供一站式服务,让开发者不再关心Spring相关配置,简化了传统的依赖注入操作。
4.0.0
com.imooc
sell
0.0.1-SNAPSHOT
jar
sell
Demo project for Spring Boot
org.springframework.boot
spring-boot-starter-parent
1.5.4.RELEASE
UTF-8
UTF-8
1.8
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
mysql
mysql-connector-java
org.springframework.boot
spring-boot-starter-data-jpa
org.projectlombok
lombok
com.google.code.gson
gson
com.github.binarywang
weixin-java-mp
3.1.0
cn.springboot
best-pay-sdk
1.2.0
org.springframework.boot
spring-boot-starter-freemarker
org.springframework.boot
spring-boot-starter-data-redis
org.springframework.boot
spring-boot-starter-websocket
org.mybatis.spring.boot
mybatis-spring-boot-starter
1.2.0
sell
org.springframework.boot
spring-boot-maven-plugin
替代传统的application.properties,配置和使用更为简洁
注意yml文件空格递进表示的层级配置,key-value格式,注意value值要在key值冒号后面空格一位,在IDE中可通过颜色高亮看出配置文件是否有误
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
username: root
password: 123456
url: jdbc:mysql://localhost:3306/sell?characterEncoding=utf-8&useSSL=false
jackson:
default-property-inclusion: non_null
redis:
host: ***.**.**.***
port: 6379
server:
context-path: /sell
wechat:
#公众平台id
mpAppId: wx777f4f5e7f1ccef7
#公众平台密钥
mpAppSecret: ebb4f15df7e750a88777b8b9dd88f86c
#开放平台id
openAppId: x6ad144e54af67d87
#开放平台密钥
openAppSecret: 91a2dd6s38a2bbccfb7e9
#商户号
mchId: 1409146202
#商户密钥
mchKey: c976503d34ca432c601361f969fd8d85
#商户证书路径
keyPath: /var/weixin_cert/h5.p12
#微信支付异步通知地址
notifyUrl: http://sell.natapcc/sell/pay/notify
#微信模版id
templateId:
orderStatus: e-Cqq67QxD6YNI41iRqawEYdFavW_7pc7LyEMb-yeQ
projectUrl:
wechatMpAuthorize: http://sell.natapp4.cc
wechatOpenAuthorize: http://sell.natapp4.cc
sell: http://localhost:8080
logging:
level:
com.imooc.dataobject.mapper: trace
mybatis:
mapper-locations: classpath:mapper/*.xml
在 SpringbootApplication 文件中右键 Run as -> Java Application。当看到 “Tomcat started on port(s): 8080 (http)” 字样说明启动成功。
基础注解为@SpringBootApplication,用于将该类标识和注解为启动类
当使用注解式sql语句编写时,需要在此加入注解扫描来指定mapper文件,也可以直接在application.yml文件中配置mybatis.mapper-locations:classpath:mapper/*.xml.
@EnableCaching增加对缓存的支持
package com.imooc;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
@SpringBootApplication
@MapperScan(basePackages = "com.imooc.dataobject.mapper")
@EnableCaching
public class SellApplication {
public static void main(String[] args) {
SpringApplication.run(SellApplication.class, args);
}
}
在resources文件夹下创建logback-spring.xml文件,并在application.yml文件中加入配置(如日志打印级别)
日志级别包括:TRACE 在项目中使用Lombok可以减少很多重复代码的书写。比如说getter/setter/toString等方法的编写。 打开IDEA的Setting –> 选择Plugins选项 –> 选择Browse repositories –> 搜索lombok –> 点击安装 –> 安装完成重启IDEA –> 安装成功。该插件还可以解决IntelliJ 注解@Slf4j后找不到log问题 在pom.xml文件中添加Lombok依赖 常用注解有 @Getter,@Setter,@Data 通过@Data注解可以获得该bean中的属性值 通过@ConfigurationProperties(prefix="xxx")可以指定application.yml文件中以xxx开头的配置项,并将其配置值注入到相应的变量中,实现对配置文件的读取 通过@Component 将普通pojo实例化到spring容器中,相当于配置文件中的 通过继承RuntimeException 定义一个异常类,提供构造方法完成code和message的初始化 加入@Getter注解,对外提供异常信息的获取 在spring 3.2中,新增了@ControllerAdvice 注解,可以用于定义@ExceptionHandler、@InitBinder、@ModelAttribute,并应用到所有@RequestMapping中。该注解很多时候是应用在全局异常处理中,类似本案例这种处理方式。 通过 @ExceptionHandler(value= xxxException.class)实现对异常的捕获,并获取异常的详细信息和错误码,处理后返回 通过@ResponseStatus(HttpStatus.FORBIDDEN) 设置响应状态码,具体状态码参考HttpStatus枚举类 在开发中,我们会需要使用到一些常量并且共享,为了保持常量值统一不变和避免魔幻数字的使用,需要将此常量值进行统一维护。在这里采用接口类进行常量的定义和初始化。 接口中的常量默认用public static final修饰,符合我们的需求。 通过定义枚举类可以帮助我们对项目中一些状态值或是错误码进行统一管理,并且可以通过Getter方法获取枚举值的详细信息。 AOP是spring框架中的一个重要内容。AOP称为面向切面编程,在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等待。 使用@Aspect声明一个切面类。 使用注解完成切点表达式、环绕通知、前置通知、后置通知的声明。 通过切点表达式,实现对访问请求的拦截,并进行切面中逻辑的处理,这里进行的是登录校验 @Entity声明该类为实体类,对应数据库的表,反映数据库表和Java实体间的映射关系 @DynamicUpdate 表示update对象的时候,生成动态的update语句。如果字段有更新,Hibernate才会对该字段进行更新,比如下面的updateTime字段。参考 https://blog.csdn.net/itguangit/article/details/78696767 @JsonIgnore 作用是在将ResponseBody中的Javabean返回前端过程中,被springmvc自带的jackson转化成json字符串时,忽略这个属性。对序列化也有影响。 这里通过继承JpaRepository实现对数据库的curd操作 Service业务处理层接口定义 Service业务处理层接口实现,注意加入@Service注解 @RestController注解相当于@ResponseBody + @Controller合在一起的作用。 @RequestMapping("/path") 表示访问的请求映射路径 @Valid 用于对表单的校验,如果校验不通过,BindingResult对象不为空,并可通过bindingResult.getFieldError().getDefaultMessage()方法获取错误信息 通过注解完成对表单填写信息的约束,并在不符合约束时将message信息返回 dto,util,converter的使用后续补充~
使用lombok简化bean开发
@Data
@Entity
public class SellerInfo {
@Id
private String sellerId;
private String username;
private String password;
private String openid;
}
使用JavaBean读取配置文件@Data
@ConfigurationProperties(prefix = "projectUrl")
@Component
public class ProjectUrlConfig {
/**
* 微信公众平台授权url
*/
public String wechatMpAuthorize;
/**
* 微信开放平台授权url
*/
public String wechatOpenAuthorize;
/**
* 点餐系统
*/
public String sell;
}
自定义异常
@Getter
public class SellException extends RuntimeException {
private Integer code;
public SellException(ResultEnum resultEnum){
super(resultEnum.getMessage());
this.code = resultEnum.getCode();
}
public SellException(Integer code,String message){
super(message);
this.code = code;
}
}
全局异常处理
@ControllerAdvice
public class SellExceptionHandler {
@Autowired
private ProjectUrlConfig projectUrlConfig;
@ExceptionHandler(value= SellerAuthorizeException.class)
@ResponseStatus(HttpStatus.FORBIDDEN) //设置响应状态码
public ModelAndView handlerAuthorizeException(){
return new ModelAndView("redirect:"
.concat(projectUrlConfig.getWechatOpenAuthorize())
.concat("/sell/wechat/qrAuthorize")
.concat("?returnUrl=")
.concat(projectUrlConfig.getSell())
.concat("/sell/seller/login"));
}
@ExceptionHandler(value= SellException.class)
@ResponseBody
public ResultVO handerSellerException(SellException e){
return ResultVOUtil.error(e.getCode(),e.getMessage());
}
}
设定系统常量
public interface CookieConstant {
String TOKEN = "token";
Integer EXPIRE = 7200;
}
枚举的使用
@Getter
public enum ResultEnum {
SUCCESS(0, "成功"),
PARAM_ERROR(1, "参数不正确"),
PRODUCT_NOT_EXIST(10, "商品不存在"),
PRODUCT_STOCK_ERROR(11, "商品库存不正确"),
ORDER_NOT_EXIST(12, "订单不存在"),
ORDERDETAIL_NOT_EXIST(13, "订单详情不存在"),
ORDER_STATUS_ERROR(14, "订单状态不正确"),
ORDER_UPDATE_FAIL(15, "订单更新失败"),
ORDER_DETAIL_EMPTY(16, "订单详情为空"),
ORDER_PAY_STATUS_ERROR(17, "订单支付状态不正确"),
CART_EMPTY(18, "购物车为空"),
ORDER_OWNER_ERROR(19, "该订单不属于当前用户"),
WECHAT_MP_ERROR(20, "微信公众账号方面错误"),
WXPAY_NOTIFY_MONEY_VERIFY_ERROR(21, "微信支付异步通知金额校验不通过"),
ORDER_CANCEL_SUCCESS(22, "订单取消成功"),
ORDER_FINISH_SUCCESS(23, "订单完结成功"),
PRODUCT_STATUS_ERROR(24, "商品状态不正确"),
LOGIN_FAIL(25, "登录失败, 登录信息不正确"),
LOGOUT_SUCCESS(26, "登出成功"),
;
private Integer code;
private String message;
ResultEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
}
Aspect切面
@Aspect
@Component
@Slf4j
public class SellerAuthorizeAspect {
@Autowired
private StringRedisTemplate redisTemplate;
@Pointcut("execution(public * com.imooc.controller.Seller*.*(..))" +
"&& !execution(public * com.imooc.controller.SellerUserController.*(..))")
public void verify() {}
@Before("verify()")
public void doVerify() {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
//查询cookie
Cookie cookie = CookieUtil.get(request, CookieConstant.TOKEN);
if (cookie == null) {
log.warn("【登录校验】Cookie中查不到token");
throw new SellerAuthorizeException();
}
//去redis里查询
String tokenValue = redisTemplate.opsForValue().get(String.format(RedisConstant.TOKEN_PREFIX, cookie.getValue()));
if (StringUtils.isEmpty(tokenValue)) {
log.warn("【登录校验】Redis中查不到token");
throw new SellerAuthorizeException();
}
}
}
实体的定义
@Entity
@Data
@DynamicUpdate
public class ProductInfo implements Serializable{
private static final long serialVersionUID = 6399186181668983148L;
@Id
private String productId;
/** 名字. */
private String productName;
/** 单价. */
private BigDecimal productPrice;
/** 库存. */
private Integer productStock;
/** 描述. */
private String productDescription;
/** 小图. */
private String productIcon;
/** 状态, 0正常1下架. */
private Integer productStatus = ProductStatusEnum.UP.getCode();
/** 类目编号. */
private Integer categoryType;
private Date createTime;
private Date updateTime;
@JsonIgnore
public ProductStatusEnum getProductStatusEnum() {
return EnumUtil.getByCode(productStatus, ProductStatusEnum.class);
}
}
dao层
public interface SellerInfoRepository extends JpaRepository
Service层
public interface SellerService {
/**
* 通过openid查询卖家端信息
* @param openid
* @return
*/
SellerInfo findByOpenid(String openid);
}
@Service
public class SellerServiceImpl implements SellerService{
@Autowired
private SellerInfoRepository repository;
@Override
public SellerInfo findByOpenid(String openid) {
return repository.findByOpenid(openid);
}
}
Controller层
@RestController
@RequestMapping("/buyer/order")
@Slf4j
public class BuyerOrderController {
@Autowired
private OrderService orderService;
@Autowired
private BuyerService buyerService;
//创建订单
@PostMapping("/create")
public ResultVO
表单校验
@Data
public class OrderForm {
/**
* 买家姓名
*/
@NotEmpty(message = "姓名必填")
private String name;
/**
* 买家手机号
*/
@NotEmpty(message = "手机号必填")
private String phone;
/**
* 买家地址
*/
@NotEmpty(message = "地址必填")
private String address;
/**
* 买家微信openid
*/
@NotEmpty(message = "openId必填")
private String openid;
/**
* 购物车
*/
@NotEmpty(message = "购物车不能为空")
private String items;
}