一屹篓、認(rèn)識項(xiàng)目結(jié)構(gòu)
1、項(xiàng)目基本結(jié)構(gòu)
- 基礎(chǔ)功能結(jié)構(gòu)
entitys // 存放實(shí)體類
enums // 存放枚舉類
dto // 存放入?yún)⒔Y(jié)構(gòu)
vo // 存放出參結(jié)構(gòu)
utils // 存放相關(guān)工具類
- 核心邏輯功能結(jié)構(gòu)
-controller // 基本參數(shù)校驗(yàn)
--service // 存放服務(wù)接口
---impl // 存放服務(wù)接口實(shí)現(xiàn)類(核心業(yè)務(wù)邏輯功能開發(fā))
----dao // 持久層村刨,數(shù)據(jù)增刪改查
-----provider // 動態(tài)sql拼接層亦歉,編寫動態(tài)的sql
2嘿棘、統(tǒng)一消息返回
- ErrorCodeEnum.java(存放各類錯(cuò)誤碼)
public enum ErrorCodeEnum {
/**
* 錯(cuò)誤碼
*/
ERROR(9999, "系統(tǒng)異常"),
HTTP_CONNECTION_OVERTIME(9998, "連接超時(shí)"),
FREQUENTLY_REQUEST(9003, "操作頻繁"),
INVALID_RSA_KEY(9002, "超時(shí)失效"),
TOKEN_TIMEOUT(9005, "token失效"),
INVALID_PARAMS(9001, "非法參數(shù)"),
SIGN_ERROR(9000, "簽名錯(cuò)誤"),
INVALID_STATUS(9004, "狀態(tài)不符"),
OK(200, "請求通過"),
NO(201, "請求不通過"),
TIP(202, "提示"),
private Integer code;
private String message;
ErrorCodeEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
public Integer getCode() {
return code;
}
public String getMessage() {
return message;
}
}
- ErrorCodeException.java 統(tǒng)一異常結(jié)構(gòu)
@ToString
public class ErrorCodeException extends RuntimeException {
private static final long serialVersionUID = -7638041501183925225L;
private Integer code;
public ErrorCodeException(ErrorCodeEnum errorCode, String msg) {
super(msg);
this.code = errorCode.getCode();
}
public ErrorCodeException(ErrorCodeEnum errorCode) {
super(errorCode.getMessage());
this.code = errorCode.getCode();
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
}
- SimpleMessage.java (簡易信息返回)
@Data
public class SimpleMessage implements Serializable {
private static final long serialVersionUID = -2957516153008725933L;
private Integer errorCode;
private String errorMsg;
public SimpleMessage(ErrorCodeEnum errorCode, String errorMsg) {
this.errorCode = errorCode.getCode();
this.errorMsg = errorMsg;
}
}
- MessageBean.java (豐富信息返回)
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MessageBean implements Serializable {
private static final long serialVersionUID = 7192766535561421181L;
private String errorMsg;
private Object data;
private Integer errorCode;
}
- AppResponseBodyAdvice.java 處理統(tǒng)一返回
@Slf4j
@ControllerAdvice
public class AppResponseBodyAdvice implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return MappingJackson2HttpMessageConverter.class.isAssignableFrom(converterType);
}
@SuppressWarnings("Duplicates")
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request,
ServerHttpResponse response) {
// 特殊返回類型處理
if (body instanceof SimpleMessage || body instanceof MessageBean) {
return body;
}
MessageBean messageBean = new MessageBean();
messageBean.setErrorCode(ErrorCodeEnum.OK.getCode());
messageBean.setErrorMsg(ErrorCodeEnum.OK.getMessage());
messageBean.setData(body);
return messageBean;
}
}
· ManagerExceptionHandler.java (全局異常處理)
@Slf4j
@ControllerAdvice
public class ManagerExceptionHandler {
@ExceptionHandler(value = ErrorCodeException.class)
@ResponseBody
public SimpleMessage myErrorHandler(ErrorCodeException e) {
SimpleMessage message = new SimpleMessage();
message.setErrorCode(e.getCode());
message.setErrorMsg(e.getMessage());
return message;
}
@ExceptionHandler(value = DuplicateKeyException.class)
@ResponseBody
public SimpleMessage duplicateKeyErrorHandler() {
SimpleMessage message = new SimpleMessage();
message.setErrorCode(ErrorCodeEnum.NO.getCode());
message.setErrorMsg("數(shù)據(jù)重復(fù)");
return message;
}
@ExceptionHandler(value = MaxUploadSizeExceededException.class)
@ResponseBody
public SimpleMessage fileSizeLimitErrorHandler() {
SimpleMessage message = new SimpleMessage();
message.setErrorCode(ErrorCodeEnum.NO.getCode());
message.setErrorMsg("圖片過大");
return message;
}
@ExceptionHandler(value = Exception.class)
@ResponseBody
public SimpleMessage errorHandler(Exception e, HttpServletRequest request) {
SimpleMessage message = new SimpleMessage();
message.setErrorCode(ErrorCodeEnum.ERROR.getCode());
message.setErrorMsg(ErrorCodeEnum.ERROR.getMessage());
log.error("url [{}] params [{}] error", request.getRequestURI(), JSON.toJSONString(request.getParameterMap()), e);
return message;
}
}
二、了解常用規(guī)范
1.實(shí)體類(以設(shè)備表功能為例)
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class DeviceInfo implements Serializable {
private static final long serialVersionUID = -4951578247348538266L;
/**
* 自增ID
*/
@NotNull(message = "設(shè)備ID不能為空", groups = {Update.class})
private Integer id;
/**
* 設(shè)備名稱
*/
@NotBlank(message = "設(shè)備名稱不能為空", groups = {Insert.class, Update.class})
private String deviceName;
/**
* 設(shè)備碼
*/
@NotNull(message = "設(shè)備碼不能為空", groups = {Insert.class, Update.class})
private DeviceTypeEnum deviceCode;
/**
* 二級分類ID
*/
@NotNull(message = "二級分類ID", groups = {Insert.class, Update.class})
private Integer parentId;
/**
* 圖標(biāo)地址
*/
@NotBlank(message = "圖標(biāo)地址不能為空", groups = {Insert.class, Update.class})
private String iconUrl;
/**
* 排序
*/
@NotNull(message = "排序不能為空", groups = {Insert.class, Update.class})
private Integer sort;
/**
* 創(chuàng)建人
*/
private String createNo;
/**
* 更新人
*/
private String updateNo;
/**
* 創(chuàng)建時(shí)間
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
/**
* 更新時(shí)間
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;
}
- 講解點(diǎn)
1挨稿、 Lomok 注解
2搔预、@Valid 注解 【@NotNull 、@NotBlank叶组、@NotEmpty拯田、@JsonFormat、@DateTimeFormat】
3甩十、自定義接口【Insert.class船庇、Update.class】
4、javadoc 注釋
5侣监、@Valid延伸鸭轮、多級結(jié)構(gòu)校驗(yàn)
- 注意點(diǎn)
【強(qiáng)制】1、實(shí)體類必須與表結(jié)構(gòu)完全對應(yīng)一致
【強(qiáng)制】2橄霉、實(shí)體類必須實(shí)現(xiàn)序列化接口
【強(qiáng)制】3窃爷、增、刪、改按厘、查的接口医吊,必須使用公共接口
【強(qiáng)制】4、java類逮京、各參數(shù)值必須使用 javadoc 注釋(具體注釋情況卿堂,需按照alibaba編程規(guī)范執(zhí)行)
【推薦】5、添加@JsonInclude(JsonInclude.Include.NON_NULL) 和 @Builder
2.DTO類
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class DeviceInfoDTO extends DeviceInfo implements Serializable {
private static final long serialVersionUID = 7535820681677648701L;
/**
* 當(dāng)前頁
*/
@NotNull(message = "當(dāng)前頁不能為空", groups = {PageQuery.class})
private Integer pageNumber;
/**
* 每頁的數(shù)量
*/
@NotNull(message = "每頁顯示數(shù)量不能為空", groups = {PageQuery.class})
private Integer pageSize;
}
- 講解點(diǎn)
1懒棉、extend 繼承的好處
2草描、PageQuery.class 接口
- 注意點(diǎn)
【強(qiáng)制】DTO 類必須繼承至實(shí)體類
【強(qiáng)制】DTO 類型實(shí)現(xiàn)后必須實(shí)現(xiàn)序列化接口
【強(qiáng)制】DTO 類的命名,必須最后的DTO 為大寫
【建議】在需要分頁功能中策严,pageNumber和pageSzie必傳
2.VO類
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class DeviceInfoVO extends DeviceInfo implements Serializable {
private static final long serialVersionUID = -5721621832940504628L;
/**
* 二級分類
*/
private String secondaryCategoryName;
/**
* 一級分類
*/
private String primaryCategoryName;
}
- 講解點(diǎn)
用法與DTO保持一致
- 注意點(diǎn)
【強(qiáng)制】VO 類必須繼承至實(shí)體類
【強(qiáng)制】VO 類型實(shí)現(xiàn)后必須實(shí)現(xiàn)序列化接口
【強(qiáng)制】VO 類的命名穗慕,必須最后的DTO 為大寫
3.枚舉類
public enum DeviceTypeEnum {
/**
* 智能插座
*/
SOCKET("智能插座"),
/**
* 通斷器
*/
ON_OFF("通斷器")
;
private final String str;
DeviceTypeEnum(String str) {
this.str = str;
}
/**
* 獲取key,value值
*
* @return List<CodeValuePair>
*/
public static List<CodeValuePair> getStatusMap() {
List<CodeValuePair> list = new ArrayList<>();
for (DeviceTypeEnum balanceTypeEnum : DeviceTypeEnum.values()) {
list.add(CodeValuePair.builder().code(balanceTypeEnum.name()).value(balanceTypeEnum.getStr()).build());
}
return list;
}
public String getStr() {
return str;
}
}
- 講解點(diǎn)
1妻导、使用類型枚舉的好處
2逛绵、使用枚舉的注意點(diǎn)(保持枚舉更新的一致性)
3、字符串枚舉的好處(簡述間隙鎖)
- 注意點(diǎn)
【強(qiáng)制】涉及到類型的必須使用枚舉
三栗竖、增刪改查標(biāo)準(zhǔn)化寫法
1暑脆、controller 層級
/**
* @author tangn
* @date 2021/1/9 9:46
*/
@RestController
@RequestMapping("/device")
public class DeviceController {
@Resource
private DeviceService deviceService;
/**
* 創(chuàng)建設(shè)備
*
* @param deviceInfo 設(shè)備信息
* @param result 校驗(yàn)結(jié)果
* @return SimpleMessage
*/
@RequestMapping("/createDevice")
public SimpleMessage createDevice(@Validated({Insert.class}) DeviceInfo deviceInfo,
BindingResult result) throws Exception {
if (result.hasErrors()) {
return new SimpleMessage(ErrorCodeEnum.INVALID_PARAMS, result.getAllErrors().get(0).getDefaultMessage());
}
return deviceService.createDevice(deviceInfo);
}
/**
* 獲取設(shè)備列表
*
* @param deviceInfoDTO 查詢參數(shù)
* @param result 校驗(yàn)結(jié)果
* @return List<DeviceInfoVO>
*/
@RequestMapping("/getDeviceList")
public Page<DeviceInfoVO> getDeviceList(@Validated({PageQuery.class}) DeviceInfoDTO deviceInfoDTO,
BindingResult result) {
if (result.hasErrors()) {
throw new ErrorCodeException(ErrorCodeEnum.INVALID_PARAMS, result.getAllErrors().get(0).getDefaultMessage());
}
return new Page<>(deviceService.getDeviceList(deviceInfoDTO));
}
/**
* 更新設(shè)備信息
*
* @param deviceInfo 設(shè)備信息
* @param result 校驗(yàn)結(jié)果
* @return SimpleMessage
*/
@RequestMapping("/updateDevice")
public SimpleMessage updateDevice(@Validated({Update.class}) DeviceInfo deviceInfo,
BindingResult result) throws Exception {
if (result.hasErrors()) {
return new SimpleMessage(ErrorCodeEnum.INVALID_PARAMS, result.getAllErrors().get(0).getDefaultMessage());
}
return deviceService.updateDevice(deviceInfo);
}
/**
* 刪除設(shè)備信息
*
* @param deviceId 設(shè)備ID
* @return SimpleMessage
*/
@RequestMapping("/delDevice")
public SimpleMessage updateDevice(Integer deviceId) {
if (Objects.isNull(deviceId)) {
return new SimpleMessage(ErrorCodeEnum.INVALID_PARAMS);
}
return deviceService.delDevice(deviceId);
}
}
- 講解點(diǎn)
1、頭部類的 @RequestMapping
2狐肢、@Validated({Update.class}) 根據(jù)接口標(biāo)識類型進(jìn)行參數(shù)校驗(yàn)的規(guī)范
3添吗、return 和 throw 的用法
4、controller層的使用規(guī)范
5份名、分頁查詢的使用方法(搭配DTO碟联、VO 使用)
6、注釋的使用
7僵腺、@Resource 和 @Autowired 的區(qū)別(自行理解)(https://blog.csdn.net/magi1201/article/details/82590106)
- 注意點(diǎn)
【強(qiáng)制】基本參數(shù)校驗(yàn)必須使用@Validated校驗(yàn)方式
【強(qiáng)制】必須用對return 和 throw 鲤孵,不允許滿足return情況的使用throw
【強(qiáng)制】注釋必須完善,不允許圖省事不寫參數(shù)的意義
【強(qiáng)制】controller 層級不允許出現(xiàn) Dao層的注入辰如,只能注入 Service 層
【強(qiáng)制】無特殊用途普监,必須使用@Resuorce注解,不能使用@Autowired注解
【建議】操作類型的返回琉兜,使用SimpleMessage
2凯正、service 層級
/**
* @author tangn
* @date 2021/1/9 9:47
*/
public interface DeviceService {
/**
* 創(chuàng)建設(shè)備
*
* @param deviceInfo 設(shè)備信息
* @return SimpleMessage
* @throws Exception
*/
SimpleMessage createDevice(DeviceInfo deviceInfo) throws Exception;
/**
* 獲取設(shè)備列表
*
* @param deviceInfoDTO 查詢參數(shù)
* @return List<DeviceInfoVO>
*/
List<DeviceInfoVO> getDeviceList(DeviceInfoDTO deviceInfoDTO);
/**
* 更新設(shè)備信息
*
* @param deviceInfo 設(shè)備信息
* @return SimpleMessage
* @throws Exception
*/
SimpleMessage updateDevice(DeviceInfo deviceInfo) throws Exception;
/**
* 刪除設(shè)備
*
* @param deviceId 設(shè)備ID
* @return SimpleMessage
* @throws Exception
*/
SimpleMessage delDevice(Integer deviceId);
}
- 講解點(diǎn)
為什么要通過接口形式進(jìn)行service層編寫?
2豌蟋、impl 層級
/**
* @author tangn
* @date 2021/1/9 9:48
*/
@Service
@Slf4j
public class DeviceServiceImpl implements DeviceService {
@Resource
private CategoryDao categoryDao;
@Resource
private DeviceDao deviceDao;
@Resource
private StoreDevicesDao storeDevicesDao;
@Resource
private AdminService adminService;
/**
* 創(chuàng)建設(shè)備
*
* @param deviceInfo 設(shè)備信息
* @return SimpleMessage
*/
@Override
public SimpleMessage createDevice(DeviceInfo deviceInfo) throws Exception {
AccountVO currentAccount = adminService.getCurrentAdmin();
// 二級分類檢測
if (categoryDao.checkSecondaryCategoryExistById(deviceInfo.getParentId()) == 0) {
return new SimpleMessage(ErrorCodeEnum.NO, "查詢不到二級分類");
}
// 設(shè)置賬戶
deviceInfo.setCreateNo(currentAccount.getPhoneNo());
// 創(chuàng)建商品
deviceDao.createDeviceInfo(deviceInfo);
return new SimpleMessage(ErrorCodeEnum.OK, "創(chuàng)建設(shè)備成功");
}
/**
* 獲取設(shè)備列表
*
* @param deviceInfoDTO 查詢參數(shù)
* @return List<DeviceInfo>
*/
@Override
public List<DeviceInfoVO> getDeviceList(DeviceInfoDTO deviceInfoDTO) {
PageHelper.startPage(deviceInfoDTO.getPageNumber(), deviceInfoDTO.getPageSize());
return deviceDao.getDeviceList(deviceInfoDTO);
}
/**
* 更新設(shè)備信息
*
* @param deviceInfo 設(shè)備信息
* @return SimpleMessage
*/
@Override
public SimpleMessage updateDevice(DeviceInfo deviceInfo) throws Exception {
AccountVO currentAccount = adminService.getCurrentAdmin();
// 設(shè)備檢測
if (deviceDao.checkDeviceById(deviceInfo.getId()) == 0) {
return new SimpleMessage(ErrorCodeEnum.NO, "查詢不到該設(shè)備");
}
// 二級分類檢測
if (categoryDao.checkSecondaryCategoryExistById(deviceInfo.getParentId()) == 0) {
return new SimpleMessage(ErrorCodeEnum.NO, "查詢不到二級分類");
}
deviceInfo.setUpdateNo(currentAccount.getPhoneNo());
// 更新設(shè)備
deviceDao.updateDeviceInfo(deviceInfo);
return new SimpleMessage(ErrorCodeEnum.OK, "更新成功");
}
/**
* 刪除設(shè)備
*
* @param deviceId 設(shè)備ID
* @return SimpleMessage
*/
@Override
public SimpleMessage delDevice(Integer deviceId) {
// 設(shè)備檢測
if (deviceDao.checkDeviceById(deviceId) == 0) {
return new SimpleMessage(ErrorCodeEnum.NO, "查詢不到該設(shè)備");
}
// 檢查設(shè)備是否被門店綁定
if (storeDevicesDao.checkDeviceBinded(deviceId) > 0) {
return new SimpleMessage(ErrorCodeEnum.NO, "該設(shè)備已經(jīng)被門店綁定廊散,請先移除");
}
deviceDao.delDeviceInfo(deviceId);
return new SimpleMessage(ErrorCodeEnum.OK, "刪除成功");
}
}
- 講解點(diǎn)
1、@Slf4j 日志打印注解
2梧疲、數(shù)據(jù)是否存在校驗(yàn)(count)
3允睹、PageHelper 使用
4运准、注釋的使用
- 注意點(diǎn)
【強(qiáng)制】參數(shù)的真實(shí)性校驗(yàn)一定要做,不要相信任何傳過來的值
【強(qiáng)制】編寫每句代碼時(shí)缭受,操作每個(gè)數(shù)據(jù)時(shí)胁澳,一定要確定該值是否為空,禁止出現(xiàn)空指針異常
【強(qiáng)制】return 和 throw 一定要使用正確(再次強(qiáng)調(diào)9嵯选L蕖)
【強(qiáng)制】數(shù)據(jù)庫返回的值慢洋,一定要做期望值校驗(yàn)(后面會講解期望值校驗(yàn)注解)
【強(qiáng)制】方法中定義的變量要使用非包裝類型接受塘雳,(例:使用int 不用 Integer )
3、dao層級
/**
* @author tangn
* @date 2021/1/19 17:00
*/
@Mapper
public interface DeviceDao {
/**
* 根據(jù)二級分類ID檢查設(shè)備是否存在
*
* @param parentId 分類ID
* @return int
*/
@Select("SELECT COUNT(*) FROM device_info WHERE parent_id = #{parentId}")
int checkDeviceByParentId(@Param("parentId") Integer parentId);
/**
* 檢查設(shè)備信息
*
* @param id 設(shè)備ID
* @return int
*/
@Select("SELECT COUNT(*) FROM device_info WHERE id = #{id}")
int checkDeviceById(@Param("id") Integer id);
/**
* 創(chuàng)建設(shè)備信息
*
* @param deviceInfo 設(shè)備信息
* @return int
*/
@Insert("INSERT INTO device_info (" +
"device_name," +
"device_code," +
"parent_id," +
"icon_url," +
"sort," +
"create_time," +
"create_no) VALUES (" +
"#{dto.deviceName}," +
"#{dto.deviceCode}," +
"#{dto.parentId}," +
"#{dto.iconUrl}," +
"#{dto.sort}," +
"now()," +
"#{dto.createNo})")
@ReturnCheck(info = "創(chuàng)建設(shè)備信息失敗")
int createDeviceInfo(@Param("dto") DeviceInfo deviceInfo);
/**
* 獲取設(shè)備信息
*
* @param deviceInfoDTO 查詢參數(shù)
* @return List<DeviceInfoVO>
*/
@SelectProvider(type = DeviceDaoProvider.class, method = "getDeviceList")
List<DeviceInfoVO> getDeviceList(@Param("dto") DeviceInfoDTO deviceInfoDTO);
/**
* 更新設(shè)備信息
*
* @param deviceInfo 設(shè)備信息
* @return int
*/
@Update("UPDATE device_info set " +
"device_name = #{dto.deviceName}," +
"device_code = #{dto.deviceCode}," +
"parent_id = #{dto.parentId}," +
"icon_url = #{dto.iconUrl}," +
"sort = #{dto.sort}," +
"update_no = #{dto.updateNo}," +
"update_time = now() " +
"WHERE id = #{dto.id} ")
@ReturnCheck(info = "更新設(shè)備信息失敗")
int updateDeviceInfo(@Param("dto") DeviceInfo deviceInfo);
/**
* 刪除設(shè)備信息
*
* @param id 設(shè)備ID
* @return int
*/
@Delete("DELETE FROM device_info WHERE id = #{id}")
@ReturnCheck(info = "刪除設(shè)備失敗")
int delDeviceInfo(@Param("id") Integer id);
/**
* 獲取設(shè)備信息
*
* @param id 設(shè)備ID
* @return DeviceInfo
*/
@Select("SELECT id,device_name FROM device_info WHERE id = #{id}")
DeviceInfo getDeviceInfo(@Param("id") Integer id);
}
- 講解點(diǎn)
1.使用count(*)校驗(yàn)數(shù)據(jù)真實(shí)性
2.@ReturnCheck 注解使用
3.@Select 普筹、@Update 败明、@Delete 的使用(尤其注意@Update 和 @Delete )
3.@SelectProvider 使用 type 和 method
4.利用映射轉(zhuǎn)VO
5.無特殊需求返回值無須添加 as (mybatis配置)
- 注意點(diǎn)
【強(qiáng)制】插入及更新時(shí)不能忘記創(chuàng)建時(shí)間(人)、更新時(shí)間(人)
【強(qiáng)制】無特殊需求太防,不允許創(chuàng)建時(shí)插入更新時(shí)間
【強(qiáng)制】影響行數(shù)必須使用非包裝類型接受(使用 int 而非 Integer )
【建議】更新時(shí)妻顶,更新依據(jù)字段盡可能落在 主鍵或唯一索引上
【建議】增加@ReturnCheck 注解 和 @ReturnListCheck 注解減少serivce層判斷
3、provider層級
/**
* @author tangn
* @date 2021/1/20 17:29
*/
public class DeviceDaoProvider {
/**
* 獲取設(shè)備列表
*
* @param map 查詢參數(shù)
* @return String
*/
public String getDeviceList(HashMap<String, DeviceInfoDTO> map) {
StringBuilder sql = new StringBuilder();
DeviceInfoDTO deviceInfoDTO = map.get("dto");
sql.append("SELECT " +
" a.id," +
" a.device_name," +
" a.device_code," +
" a.parent_id," +
" a.icon_url," +
" a.sort," +
" d.name as 'createNo'," +
" e.name as 'updateNo'," +
" a.create_time," +
" a.update_time, " +
" b.category_name AS 'secondaryCategoryName', " +
" c.category_name AS 'primaryCategoryName' " +
"FROM " +
" `device_info` a " +
" JOIN device_secondary_category b ON a.parent_id = b.id " +
" JOIN device_primary_category c ON b.parent_id = c.id " +
" LEFT JOIN shiro_account d ON a.create_no = d.phone_no and d.plat_type = 0 " +
" LEFT JOIN shiro_account e ON a.update_no = e.phone_no and e.plat_type = 0 " +
"WHERE " +
" 1 = 1 ");
// 設(shè)備名稱
if (StringUtils.isNotBlank(deviceInfoDTO.getDeviceName())) {
sql.append(" AND INSTR(a.device_name,#{dto.deviceName}) > 0 ");
}
// 二級分類
if (Objects.nonNull(deviceInfoDTO.getParentId())) {
sql.append(" AND a.parent_id = #{dto.parentId} ");
}
sql.append(" ORDER BY a.create_time DESC ");
return sql.toString();
}
}
- 講解點(diǎn)
1.DTO 內(nèi)取值的好處
2.WHERE 1 = 1 的妙處
3.使用工具類進(jìn)行參數(shù)值的判斷
4.使用 INSTR 進(jìn)行模糊查詢
- 規(guī)范點(diǎn)
【強(qiáng)制】使用工具類對參數(shù)進(jìn)行判別
【建議】使用DTO 作為參數(shù)的攜帶體
四蜒车、來點(diǎn)干的讳嘱?
1、事務(wù)的使用
/**
* 確認(rèn)發(fā)放
*
* @param shopOrderDTO 參數(shù)
* @return : com.orangeconvenient.common.entity.MessageBean<java.time.LocalDateTime>
*/
@Transactional(rollbackFor = Exception.class)
@Override
public MessageBean<String> confirmRelease(ShopOrderDTO shopOrderDTO) {
ShopOrder shopOrder = Optional.ofNullable(storeOrderDao.getByIdAndStore(shopOrderDTO.getId(), shopOrderDTO.getStoreNo()))
.orElseThrow(() -> new ErrorCodeException(ErrorCodeEnum.NO, "訂單不存在"));
if (ShopOrderStatusEnum.RECEIVED.equals(shopOrder.getOrderStatus())) {
return new MessageBean<>(ErrorCodeEnum.EXT_ASSEMBLE_ORDER_CONSUMED, Objects.nonNull(shopOrder.getSuccessTime()) ? LocalDateTimeUtil.formatDateTime(shopOrder.getSuccessTime()) : null, "此訂單顧客已取貨,請不要重復(fù)發(fā)放!");
}
if (ShopOrderStatusEnum.MEMBER_CANCEL.equals(shopOrder.getOrderStatus()) ||
ShopOrderStatusEnum.STORE_CANCEL.equals(shopOrder.getOrderStatus()) ||
ShopOrderStatusEnum.RAISE_OVER_CANCEL.equals(shopOrder.getOrderStatus())) {
return new MessageBean<>(ErrorCodeEnum.NO, "此訂單已被取消,請不要發(fā)放商品!");
}
if (!ShopOrderStatusEnum.PENDING.equals(shopOrder.getOrderStatus())) {
return new MessageBean<>(ErrorCodeEnum.NO, "訂單狀態(tài)不正確");
}
shopOrder.setSuccessTime(LocalDateTime.now());
// 設(shè)置出庫商品的數(shù)量
shopOrder.setOutGoodsNum(orderDao.getGoodsNumByOrderNo(shopOrder.getOrderNo(), ShopOrderInfoStatusEnum.PENDING));
orderDao.updateOrderStatus(shopOrder, ShopOrderStatusEnum.RECEIVED);
List<ShopOrderInfoVO> shopOrderInfoList = orderDao.getCanRefundOrderInfoList(shopOrder.getId(), ShopOrderInfoStatusEnum.PENDING);
if (CollectionUtils.isEmpty(shopOrderInfoList)) {
return new MessageBean<>(ErrorCodeEnum.NO, "沒有待自提的訂單明細(xì)");
}
if (orderDao.updateInfoStatusByOrderId(shopOrder.getId(), ShopOrderInfoStatusEnum.RECEIVED, ShopOrderInfoStatusEnum.PENDING, null) != shopOrderInfoList.size()) {
throw new ErrorCodeException(ErrorCodeEnum.NO, "訂單明細(xì)狀態(tài)修改失敗");
}
// 更新實(shí)際出庫的訂單詳情
storeOrderDao.updateInfoOutStatusByIds(shopOrderInfoList.size(), shopOrderInfoList.stream().map(ShopOrderInfo::getId).collect(Collectors.toList()), true);
User user = Optional.ofNullable(userDao.queryUserById(shopOrder.getUserId())).orElseThrow(() -> new ErrorCodeException(ErrorCodeEnum.NO, "會員不存在"));
//初始化評價(jià)
memberCardOrderDao.insertEvaluateRecord(EvaluateRecord.builder()
.orderNo(shopOrder.getOrderNo())
.unionId(user.getUnionId())
.userId(shopOrder.getUserId())
.consumeTime(shopOrder.getSuccessTime())
.merchantNo(shopOrder.getStoreNo())
.totalAmount(shopOrderInfoList.stream().map(ShopOrderInfo::getActualPrice).reduce(BigDecimal.ZERO, BigDecimal::add))
.evaluateRecordType(EvaluateRecordTypeEnum.MALL_ORDER).build());
// 獲取門店信息
Store store = storeDao.getByStoreNo(shopOrder.getStoreNo());
//初始化售后信息
ShopOrderAfterSale shopOrderAfterSale = ShopOrderAfterSale.builder()
.orderId(shopOrder.getId())
.userId(shopOrder.getUserId())
.afterSaleOrderNo(Constant.SHOP_ORDER_AFTER_SALE_PREFIX + CheckUtil.fillZero(user.getId().longValue(), 5) + System.nanoTime()).build();
afterSaleOrderDao.insertAfterSaleOrder(shopOrderAfterSale);
// 初始化模板消息備注信息
StringBuilder remark = new StringBuilder(200);
givenUserIntegral(user, shopOrderInfoList, shopOrder.getStoreNo(), shopOrder.getOrderNo(), shopOrder, remark);
// 消費(fèi)返券
consumeReturnCoupon(shopOrder.getId(), user, remark, shopOrderDTO.getStoreNo());
vitalityChangeService.doVitality(shopOrder.getUserId(), ShopCouponWeeklyContentTypeEnum.SHOP_CONSUME, null);
//發(fā)送新版評價(jià)
accessTokenComponent.sendCommonEvaluateMessage(user.getOpenId(),
user.getUnionId(), shopOrder.getOrderNo(), Objects.nonNull(store) ? store.getBusinessName() : "",
LocalDateTime.now(), remark);
return new MessageBean<>(ErrorCodeEnum.OK, "取貨成功,請將商品發(fā)放給顧客");
}
- 講解點(diǎn)
1.事務(wù)的使用場景
2.為什么我的事務(wù)不生效酿愧?
3.@Builder使用
- 注意點(diǎn)
【強(qiáng)制】不要使用編程式事務(wù)
【建議】開啟事務(wù)后沥潭,要考慮在合適的時(shí)機(jī)拋出事務(wù),讓其回滾
【建議】多層事務(wù)嵌套嬉挡,要考慮回滾情況
2钝鸽、for update 使用(悲觀鎖)
/**
* 獲取最后一條會員積分記錄
*
* @param userId 會員id
* @return 積分
*/
@Select("select integral from tbl_user_integral where user_id=#{userId} for update")
@Options(timeout = 3)
Long getLastIntegralByUserId(@Param("userId") Long userId);
- 講解點(diǎn)
當(dāng)涉及到金額、庫存等需要保持一致性的操作時(shí)庞钢,可采用悲觀鎖進(jìn)行相應(yīng)的查詢并執(zhí)行更新
- 注意點(diǎn)
@Options(timeout = 3) 一定要加拔恰,且不能鎖數(shù)據(jù)時(shí)間過長
3、update 更新狀態(tài)
/**
* 更新閑魚訂單狀態(tài)
* @param outOrderStatus 訂單狀態(tài)
* @param orderId 訂單ID
* @param oldStatus 舊狀態(tài)
* @return int
*/
@Update("UPDATE xy_receive_order SET " +
"out_order_status = #{outOrderStatus}," +
"update_time = now() " +
"WHERE id = #{orderId} AND " +
"out_order_status = #{oldStatus} ")
@ReturnCheck(info = "更新閑魚訂單狀態(tài)失敗")
int updateOutOrderStatus(@Param("outOrderStatus") Integer outOrderStatus,
@Param("orderId") Long orderId,
@Param("oldStatus") Integer oldStatus);
- 講解點(diǎn)
當(dāng)對狀態(tài)相關(guān)數(shù)據(jù)進(jìn)行更新時(shí),需要知道數(shù)據(jù)的原狀態(tài)
- 注意點(diǎn)
【強(qiáng)制】 更新狀態(tài)時(shí)蛇受,必須限定原狀態(tài)
4斑匪、update 更新數(shù)值
/**
* 扣款
*
* @param id 代金券接收ID
* @param orderAmount 訂單金額
* @return int
*/
@Update("UPDATE cash_voucher_receiver " +
"SET left_amount = ( left_amount - #{orderAmount} ), " +
"update_time = now( ) " +
"WHERE " +
" id = #{id} " +
" AND (left_amount - #{orderAmount}) >= 0 ")
@ReturnCheck(info = "核銷失敗")
int consumeVoucher(@Param("id") Long id, @Param("orderAmount") BigDecimal orderAmount);
- 講解點(diǎn)
- WHERE 前使用數(shù)據(jù)庫增減余額(庫存)
2.WHERE 后使用計(jì)算結(jié)果>0 進(jìn)行校驗(yàn),防止多扣(允許負(fù)庫存例外)
- 注意點(diǎn)
【強(qiáng)制】禁止在service層級計(jì)算好結(jié)果直接更新到庫
5河爹、批量操作 in 的使用方法
/**
* 批量插入用戶信息
*
* @param userInfoList 用戶信息列表
* @return 影響的行數(shù)
*/
@Insert("<script>" +
" insert into tbl_user_info " +
" (user_id, recent_consume_date, store_consume_time, " +
" shop_consume_time, assemble_consume_time, " +
" store_consume_date_collect, shop_consume_date_collect, " +
" assemble_consume_date_collect, create_time) " +
" values " +
" <foreach collection=\"list\" index=\"index\" item=\"info\" open=\"\" separator=\",\" close=\"\"> " +
" ( " +
" #{info.userId}, " +
" #{info.recentConsumeDate}, " +
" #{info.storeConsumeTime}, " +
" #{info.shopConsumeTime}, " +
" #{info.assembleConsumeTime}, " +
" #{info.storeConsumeDateCollect}, " +
" #{info.shopConsumeDateCollect}, " +
" #{info.assembleConsumeDateCollect}, " +
" NOW() " +
" )" +
" </foreach>" +
"</script>")
int insertUserInfo(@Param("list") List<UserInfo> userInfoList);
- 講解點(diǎn)
1.<script> 標(biāo)簽 替代 xml 文件
2.<foreach> 標(biāo)簽的使用
3.使用批量插入的好處
4.使用批量插入的風(fēng)險(xiǎn)點(diǎn)
- 注意點(diǎn)
【強(qiáng)制】在使用in進(jìn)行動態(tài)sql拼接時(shí),一定要考慮應(yīng)用場景插入的條數(shù)揪阶,必要時(shí)昌抠,需要在sevice層做分割。