心得體會
最近通過慕課網學習了《基於springboot企業微信點餐系統項目》,乾貨滿滿,既有springboot知識的學習,又有業務邏輯的處理體會。更多的是接觸了企業級開發過程中各種編程心得和技巧,能夠很大程度的幫助我們規範開發的流程,使得我們的代碼更加優雅和實現可維護性。
git地址:https://gitee.com/jiayuan1234/sell
關於springcloud的可以看參考這篇文章:https://blog.csdn.net/zhang_jiayuan/article/details/83994948
基於springboot
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這一層級,再以下均爲自定義
maven依賴
spring-boot-starter-*起步依賴是SpringBoot核心之處,它提供了Spring和相關技術提供一站式服務,讓開發者不再關心Spring相關配置,簡化了傳統的依賴注入操作。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.imooc</groupId>
<artifactId>sell</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>sell</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-mp</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>cn.springboot</groupId>
<artifactId>best-pay-sdk</artifactId>
<version>1.2.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.2.0</version>
</dependency>
</dependencies>
<build>
<finalName>sell</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.yml配置
替代傳統的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一鍵部署和啓動
在 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);
}
}
使用logback日誌
在resources文件夾下創建logback-spring.xml文件,並在application.yml文件中加入配置(如日誌打印級別)
日誌級別包括:TRACE<DEBUG<INFO<WARN<ERROR,其中常用的是DEBUG、INFO和ERROR。如果logger沒有被分配級別,name它將從有被分配級別的最近的父類那裏繼承級別,root logger默認級別是DEBUG。級別在高於或等於其logger的有效級別時爲啓用,否則爲禁用。只有在請求級別大於有效級別時,該請求才會被執行,並打印或輸出日誌
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<appender name="consoleLog" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>
%d - %msg%n
</pattern>
</layout>
</appender>
<appender name="fileInfoLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>DENY</onMatch>
<onMismatch>ACCEPT</onMismatch>
</filter>
<encoder>
<pattern>
%msg%n
</pattern>
</encoder>
<!--滾動策略-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--路徑 /var/log/tomcat/sell/info.%d.log-->
<fileNamePattern>/D://MyData//workspace//IDEA//sell-log//info.%d.log</fileNamePattern>
</rollingPolicy>
</appender>
<appender name="fileErrorLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
<encoder>
<pattern>
%msg%n
</pattern>
</encoder>
<!--滾動策略-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--路徑 /var/log/tomcat/sell/error.%d.log -->
<fileNamePattern>/D://MyData//workspace//IDEA//sell-log//error.%d.log</fileNamePattern>
</rollingPolicy>
</appender>
<root level="info">
<appender-ref ref="consoleLog" />
<appender-ref ref="fileInfoLog" />
<appender-ref ref="fileErrorLog" />
</root>
</configuration>
使用lombok簡化bean開發
在項目中使用Lombok可以減少很多重複代碼的書寫。比如說getter/setter/toString等方法的編寫。
打開IDEA的Setting –> 選擇Plugins選項 –> 選擇Browse repositories –> 搜索lombok –> 點擊安裝 –> 安裝完成重啓IDEA –> 安裝成功。該插件還可以解決IntelliJ 註解@Slf4j後找不到log問題
在pom.xml文件中添加Lombok依賴
常用註解有 @Getter,@Setter,@Data
@Data
@Entity
public class SellerInfo {
@Id
private String sellerId;
private String username;
private String password;
private String openid;
}
使用JavaBean讀取配置文件
通過@Data註解可以獲得該bean中的屬性值
通過@ConfigurationProperties(prefix="xxx")可以指定application.yml文件中以xxx開頭的配置項,並將其配置值注入到相應的變量中,實現對配置文件的讀取
通過@Component 將普通pojo實例化到spring容器中,相當於配置文件中的<bean id="" class=""/>
@Data
@ConfigurationProperties(prefix = "projectUrl")
@Component
public class ProjectUrlConfig {
/**
* 微信公衆平臺授權url
*/
public String wechatMpAuthorize;
/**
* 微信開放平臺授權url
*/
public String wechatOpenAuthorize;
/**
* 點餐系統
*/
public String sell;
}
自定義異常
通過繼承RuntimeException 定義一個異常類,提供構造方法完成code和message的初始化
加入@Getter註解,對外提供異常信息的獲取
@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;
}
}
全局異常處理
在spring 3.2中,新增了@ControllerAdvice 註解,可以用於定義@ExceptionHandler、@InitBinder、@ModelAttribute,並應用到所有@RequestMapping中。該註解很多時候是應用在全局異常處理中,類似本案例這種處理方式。
通過 @ExceptionHandler(value= xxxException.class)實現對異常的捕獲,並獲取異常的詳細信息和錯誤碼,處理後返回
通過@ResponseStatus(HttpStatus.FORBIDDEN) 設置響應狀態碼,具體狀態碼參考HttpStatus枚舉類
@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 static final修飾,符合我們的需求。
public interface CookieConstant {
String TOKEN = "token";
Integer EXPIRE = 7200;
}
枚舉的使用
通過定義枚舉類可以幫助我們對項目中一些狀態值或是錯誤碼進行統一管理,並且可以通過Getter方法獲取枚舉值的詳細信息。
@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切面
AOP是spring框架中的一個重要內容。AOP稱爲面向切面編程,在程序開發中主要用來解決一些系統層面上的問題,比如日誌,事務,權限等待。
使用@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聲明該類爲實體類,對應數據庫的表,反映數據庫表和Java實體間的映射關係
@DynamicUpdate 表示update對象的時候,生成動態的update語句。如果字段有更新,Hibernate纔會對該字段進行更新,比如下面的updateTime字段。參考 https://blog.csdn.net/itguangit/article/details/78696767
@JsonIgnore 作用是在將ResponseBody中的Javabean返回前端過程中,被springmvc自帶的jackson轉化成json字符串時,忽略這個屬性。對序列化也有影響。
@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層
這裏通過繼承JpaRepository實現對數據庫的curd操作
public interface SellerInfoRepository extends JpaRepository<SellerInfo, String> {
SellerInfo findByOpenid(String openid);
}
Service層
Service業務處理層接口定義
public interface SellerService {
/**
* 通過openid查詢賣家端信息
* @param openid
* @return
*/
SellerInfo findByOpenid(String openid);
}
Service業務處理層接口實現,注意加入@Service註解
@Service
public class SellerServiceImpl implements SellerService{
@Autowired
private SellerInfoRepository repository;
@Override
public SellerInfo findByOpenid(String openid) {
return repository.findByOpenid(openid);
}
}
Controller層
@RestController註解相當於@ResponseBody + @Controller合在一起的作用。
@RequestMapping("/path") 表示訪問的請求映射路徑
@Valid 用於對錶單的校驗,如果校驗不通過,BindingResult對象不爲空,並可通過bindingResult.getFieldError().getDefaultMessage()方法獲取錯誤信息
@RestController
@RequestMapping("/buyer/order")
@Slf4j
public class BuyerOrderController {
@Autowired
private OrderService orderService;
@Autowired
private BuyerService buyerService;
//創建訂單
@PostMapping("/create")
public ResultVO<Map<String,String>> create(@Valid OrderForm orderForm,
BindingResult bindingResult){
if(bindingResult.hasErrors()){
log.error("【創建訂單】參數不正確,orderForm={}",orderForm);
throw new SellException(ResultEnum.PARAM_ERROR.getCode(),bindingResult.getFieldError().getDefaultMessage());
}
OrderDTO orderDTO = OrderForm2OrderDTOConverter.convert(orderForm);
if(CollectionUtils.isEmpty(orderDTO.getOrderDetailList())){
log.error("【創建訂單】購物車不能爲空");
throw new SellException(ResultEnum.CART_EMPTY);
}
OrderDTO createResult = orderService.create(orderDTO);
Map<String,String> map = new HashMap<String,String>();
map.put("orderId",createResult.getOrderId());
return ResultVOUtil.success(map);
}
}
表單校驗
通過註解完成對錶單填寫信息的約束,並在不符合約束時將message信息返回
@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;
}
dto,util,converter的使用後續補充~