跳至主要內容

C004.23届0525怪

trydofor原创技债大约 4 分钟

C004.23届0525怪

1.CompanyManageController.java 70-74

gigax 6697d36f75f4ced94545489e61f8c3781ccf5ecd

@RequestMapping("/admin/parcel/company-name.json")
public R<?> searchCompanyName() {
    Map<Long, String> map = companySearchService.searchName(TenantUtil.getTenantable());
    return R.okData(map);
}
// 修改后
@PostMapping("/admin/parcel/company-name.json")
public R<Map<Long, String>> searchCompanyName() {
    final Map<Long, String> map = companySearchService.searchName(TenantUtil.getTenantable());
    return R.okData(map);
}

明确请求 PostMapping

RequestMapping包括了 Get, Post, Put, Delete, Patch,对Swagger文档不友好,也容易造成乱用。 按wings的RestHalf约定,除了资源类和Oauht使用GET外,全部使用Post。

明确返回值 R的泛型

明确R的泛型,有利于Swagger自动推导response的示例,所以应该避免R<?>,一般呈现以下形式。

  • R<Void> - 无data,可以返回R.OK
  • R<DataType> - 明确的data类型
  • PageResult<DataType> - 分页的,明确的Data类型

2.CompanyManageController.java 101-110

gigax 6697d36f75f4ced94545489e61f8c3781ccf5ecd

@RequestMapping(value = "/admin/company/delete.json")
public R<?> deleteInfo(@RequestParam(value = "userId") long userId) {
    try {
        companySearchService.deleteCustomer(userId);
        return R.ok("删除成功!");
    }
    catch (Exception e) {
        log.error("/admin/company/delete.json", e);
        return R.ng("删除失败!");
    }
}

统一处理异常

没有必要在Controller中处理通用异常,交给HandlerExceptionResolver统一处理。

  • CodeException - 默认无栈异常,属正常业务流,自动转换I18n
  • Default - 默认处理未识别异常为预设的得response

3.DashboardController.java 56-59

gigax 6697d36f75f4ced94545489e61f8c3781ccf5ecd

@PostMapping(value = "/dashboard/out/export.json")
@DoubleKill
public void export(@RequestBody DashboardService.QueryDate queryDate, HttpServletResponse response)

DoubleKill的使用注意Key

DoubleKill基于AOP,建议使用SpEL指定key,否则方法的参数需要正确实现equals, hashCode,才能正确获取锁。

4.DashboardServiceImpl.java 332-337

gigax 6697d36f75f4ced94545489e61f8c3781ccf5ecd

private void export(List<ExcelInfo> excelInfos) throws IOException {
    ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    assert servletRequestAttributes != null;
    HttpServletResponse response = servletRequestAttributes.getResponse();
    assert response != null;
    EasyExcel.write(response.getOutputStream(), ExcelInfo.class)

// 修改后
private void export(@NotNull List<ExcelInfo> excelInfos, @NotNull OutputStream output) throws IOException {
    EasyExcel.write(routput, ExcelInfo.class)

严禁跨层,严禁隐式传值

  • Web层对象不可以进入Service层,违反基本约定。
  • 从ContextHolder取值属于方法泄露,违反基本约定。
  • assert 改为前置检查,以CodeException中断处理。
  • @NotNull 提供IDE检查,在编写时提供质量。

5.OrderController.java 103-106

gigax 64077da17ce4920f6c8d7088664816bb0f36a36b

@PostMapping("/parcel/truck/check-list.json")
public R<Collection<ParcelCheckQueryVo>> checkList(@RequestParam Long id) {
    List<OwtLadingOrder> data = owtLadingOrderDao.fetchByTruckOrderId(id);
    return R.okData(parcelHelper.transferParcelCheckQueryVo(data));
}

Controller中不可使用Dao

  • 建议包装到Service,避免违反Mvc,Service/Dao的分层约定
  • 若简单read,可把Dao当做Service使用,但按需select,不可泄露非必要的po信息

6.ParcelBindController.java 147-158

gigax 64077da17ce4920f6c8d7088664816bb0f36a36b

@PostMapping("/truck/truck/bindPlate.json")
public R<?> bindPlate(@PageDefault(size = 20) PageQuery pageQuery,
                      @RequestParam(required = false) Long supplier,
                      @RequestParam(required = false) String searchType,
                      @RequestParam(required = false) String container,
                      @RequestParam(required = false) Long companyId,
                      @RequestParam(required = false) String address,
                      @RequestParam(required = false) Boolean isUpdate,
                      @RequestParam(required = false) Integer status,
                      @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime start,
                      @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime end,
                      @RequestParam(required = false) LocalDate startDate,
                      @RequestParam(required = false) LocalDate endDate,
                      @RequestParam(required = false) String arriveDtStatus,
                      @RequestParam(required = false) Boolean signatureFileStatus,
                      @RequestParam(required = false) Long dismantle,
                      @RequestParam Boolean isTruck) {

使用对象取代长参数列表

  • @RequestBody BizIns ins 用于承载业务数据
  • @PageDefault(size = 20) PageQuery pq 使用queryString接受参数,享受别名功能

以上为wings的约定,建议前端api使用TypeScript封装,如利用Axios请求及拦截处理。

  • @RequestParam Boolean isTruck 使用 boolean,因为必填不能为null
  • 使用Json包装业务数据,可以更好的支持enum,datatime的解析

7.TempSearchController.java 49-60

gigax 350877671f7ee1cbc9f444b99d822bc20257c415

// type enum
if (type == null) {
    return tempSearchService.getList(pageQuery, ladingIds);
} else if (type.equals(QueryType.AirBol)) {
    return tempSearchService.getByAirBol(condition, pageQuery, ladingIds);
} else if (type.equals(QueryType.SeaBol)) {
    return tempSearchService.getBySeaBol(condition, pageQuery, ladingIds);
} else if (type.equals(QueryType.Mark)) {
    return tempSearchService.getByMark(condition, pageQuery, ladingIds);
} else {
    return R.ng("查询失败!");
}

==表示常量比较

enum类型使用==比较,显示的展示常量类型,而equals一般用于引用对象的值比较。

加强版switch替换多if

// 大多数情况为null。并列条件应该按照出现概率的从高到低,从上到下写代码
if (type == null) return tempSearchService.getList(pageQuery, ladingIds);

return switch (type) {
    // case null -> tempSearchService.getList(pageQuery, ladingIds); // pattern matching
    case AirBol -> tempSearchService.getByAirBol(condition, pageQuery, ladingIds);
    case SeaBol -> tempSearchService.getBySeaBol(condition, pageQuery, ladingIds);
    case Mark -> tempSearchService.getByMark(condition, pageQuery, ladingIds);
    default -> R.ng(PageResult.empty(), "查询失败!");
};

加强版switch不支持null,否则会出现一下NPE(在17的preview或21可支持pattern matching)

Cannot invoke "QueryType.ordinal()" because "type" is null

8.UserDetailCache.java 43-47

gigax 350877671f7ee1cbc9f444b99d822bc20257c415

@Cacheable(value = "userDetail")
public OwtUserDetail getByUserIdFromCache(Long id) {
    Optional<OwtUserDetail> fromDb = getFromDb(id);
    return fromDb.orElseThrow(() -> new CodeException(CommonErrorEnum.AssertNotFound1, id));
}
private Optional<OwtUserDetail> getFromDb(Long id) {
    logger.info("find from db");
    Condition condition = OwtUserDetailTable.OwtUserDetail.onlyLive(OwtUserDetailTable.OwtUserDetail.UserId.eq(id));
    return Optional.ofNullable(owtUserDetailDao.fetchOne(OwtUserDetailTable.OwtUserDetail, condition));
}

// 修改后
@Cacheable(value = UserDetail.CacheNameDetail)
public OwtUserDetail getByUserIdFromCache(Long id) {
    log.debug("find from db");
    final OwtUserDetail detail = owtUserDetailDao.fetchOne(t -> t.onlyLive(t.UserId.eq(id)));
    StateAssert.notNull(detail, CommonErrorEnum.AssertNotFound1, id);
    return detail;
}
  • 过度拆分方法,getFromDb 仅一处使用,可以合到一起
  • 没必要使用Optional,非lambda的反应式
  • Cacheable尽量使用预定义常量,使用@CacheConfig定义
  • logger使用lombok的@Slf4j即可
  • 使用StateAssert做CodeException的检查
  • jooq不要使用table的静态实例,使用dao可正确处理别名
  • 简单查询,采用Dao+lambda即可