examples-ylsw-bw.md 15 KB

CRUD 示例代码(ylsw-bw / ledger · meter)

以下示例提炼自 business/water-meter/src/main/java/com/tofly/wm/ledger/meter/,生成新模块时替换 {Domain}{domain}TF_WM_{DOMAIN}_W 等占位符即可。

1) Controller(标准 CRUD + 权限)

package com.tofly.wm.ledger.{domain};

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.tofly.common.core.entity.ResultResponse;
import com.tofly.entity.enumertion.Permission;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RequestMapping("/{domain}")
@RestController
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
@RequiredArgsConstructor
@Api(tags = "{业务中文名}")
public class {Domain}Controller {

    {Domain}Service {domain}Service;

    @GetMapping("/page")
    @ApiOperation("{业务中文名}分页查询")
    public ResultResponse<Page<{Domain}>> page({Domain}Query query) {
        return ResultResponse.success(this.{domain}Service.page(query));
    }

    @GetMapping("/list")
    @ApiOperation("{业务中文名}列表查询")
    public ResultResponse<List<{Domain}>> list({Domain}Query query) {
        return ResultResponse.success(this.{domain}Service.list(query));
    }

    @GetMapping("/{id}")
    @ApiOperation("{业务中文名}详情查询")
    public ResultResponse<{Domain}> getById(@PathVariable @ApiParam("主键") Long id) {
        return ResultResponse.success(this.{domain}Service.getById(id));
    }

    @PostMapping
    @ApiOperation("{业务中文名}新增")
    public ResultResponse<Boolean> add(@RequestBody {Domain} entity) {
        return ResultResponse.success(this.{domain}Service.save(entity));
    }

    @PostMapping("/update")
    @ApiOperation("{业务中文名}修改")
    public ResultResponse<Boolean> update(@RequestBody {Domain} entity) {
        return ResultResponse.success(this.{domain}Service.updateById(entity));
    }

    @DeleteMapping("/deleteByIds")
    @ApiOperation("{业务中文名}删除")
    public ResultResponse<Boolean> deleteByIds(@RequestParam @ApiParam("主键,逗号分隔,如 1,2,3") String ids) {
        return ResultResponse.success(this.{domain}Service.deleteByIds(ids));
    }
}

2) Query(列表 / 分页共用查询对象)

GET /listGET /page 共用同一 Query,条件字段保持一致;/page 额外使用 PageQuery 中的 pageNumpageSize

Query 不使用 OrganizationEntry / UserNameEntry / DictEntry,部门/用户筛选用 LongString;值对象仅用于 Entity/Vo。

package com.tofly.wm.ledger.{domain};

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.tofly.entity.pojo.PageQuery;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

@Data
@ApiModel("{业务中文名}查询")
public class {Domain}Query extends PageQuery {

    @ApiModelProperty("申请部门ID")
    Long applyDeptId;

    @ApiModelProperty("示例编码")
    String code;

    @Override
    public void wrapper(LambdaQueryWrapper wrapper) {
        // 单表场景可留空,条件在 Service 的 buildWrapper 中构建
    }
}

3) Entity(BaseEntity + 业务字段)

强制:所有 @TableName 实体 extends BaseEntity;公共列由父类映射。建表时在 CREATE TABLE 中声明公共列(见 java-ylsw-bw.md § BaseEntity),勿用 ALTER TABLE 补列。

禁止在子类重复声明:createUsercreateTimeupdateUserupdateTimecreateCompanyIdcurDeptIdBoolean deletedBaseEntity 已提供则不再声明)。

package com.tofly.wm.manage.{biz};

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.tofly.entity.annotation.DictDirectory;
import com.tofly.entity.pojo.BaseEntity;
import com.tofly.entity.pojo.DictEntry;
import com.tofly.entity.pojo.FileEntryList;
import com.tofly.entity.pojo.OrganizationEntry;
import com.tofly.entity.pojo.UserNameEntry;
import com.tofly.wm.cons.MeterConstant;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AccessLevel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.FieldDefaults;

/**
 * {业务中文名}实体
 *
 * @author ...
 * @date ...
 */
@Data
@EqualsAndHashCode(callSuper = true)
@FieldDefaults(level = AccessLevel.PRIVATE)
@TableName("TF_WM_{DOMAIN}_W")
@ApiModel("{业务中文名}")
public class {Domain} extends BaseEntity {

    @TableId(value = "ID", type = IdType.ASSIGN_ID)
    @ApiModelProperty("主键")
    Long id;

    @TableField("APPLY_DEPT_ID")
    @ApiModelProperty("申请部门")
    OrganizationEntry applyDept;

    @TableField("APPLY_USER_ID")
    @ApiModelProperty("申请人")
    UserNameEntry applyUser;

    @DictDirectory(MeterConstant.DICT_METER_JJCD)
    @TableField("PRIORITY_CODE")
    @ApiModelProperty("紧急程度")
    DictEntry priority;

    @DictDirectory(MeterConstant.DICT_METER_TYPE)
    @TableField("METER_TYPE_CODE")
    @ApiModelProperty("水表类型")
    DictEntry meterType;

    @TableField(exist = false)
    @ApiModelProperty("附件")
    FileEntryList attach;
}

逻辑删除BaseEntity 未含 deleted 时在子类声明;库列仍为 DELETED):

@TableLogic
@TableField("DELETED")
@ApiModelProperty("删除状态")
Boolean deleted = false;

单附件(仅一个文件时用 FileEntry,同样不落库):

@TableField(exist = false)
@ApiModelProperty("附件")
FileEntry attach;

明细表示例(同样继承 BaseEntity):

@TableName("TF_WM_{DOMAIN}_LIST_W")
public class {Domain}List extends BaseEntity {
    @TableId(value = "ID", type = IdType.ASSIGN_ID)
    Long id;
    @TableField("PARENT_ID")
    Long parentId;
    // 仅业务字段,无 CREATE_USER/DELETED 等
}

保存时赋值(Service):

entity.setApplyDept(OrganizationEntry.of(ApplicationSession.getDepartmentId()));
entity.setApplyUser(UserNameEntry.of(ApplicationSession.getUserId()));

建表 COMMENT(关联列须写目标表)

COMMENT ON COLUMN TF_WM_{DOMAIN}_W.APPLY_DEPT_ID IS '申请部门ID,关联tf_org表的id字段';
COMMENT ON COLUMN TF_WM_{DOMAIN}_W.APPLY_USER_ID IS '申请用户ID,关联tf_user表的id字段';
COMMENT ON COLUMN TF_WM_{DOMAIN}_W.PRIORITY_CODE IS '紧急程度编号,字典类型,目录码METER_JJCD';
COMMENT ON COLUMN TF_WM_{DOMAIN}_W.TASK_STATUS_CODE IS '任务状态编号,枚举类型,TaskStatusEnum';

4) 业务枚举

package com.tofly.wm.ledger.{domain}.enums;

import com.tofly.entity.enumertion.Enumerable;
import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public enum {Domain}StatusEnum implements Enumerable<Integer> {

    ENABLED(1, "启用"),
    DISABLED(0, "停用"),
    ;

    Integer code;
    String label;

    @Override
    public Integer getKey() {
        return this.code;
    }
}

5) Mapper

package com.tofly.wm.ledger.{domain};

import com.github.yulichang.base.MPJBaseMapper;

public interface {Domain}Mapper extends MPJBaseMapper<{Domain}> {
}

6) Service + ServiceImpl(单表 Wrappers + PageHelper)

package com.tofly.wm.ledger.{domain};

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.yulichang.base.MPJBaseService;

public interface {Domain}Service extends MPJBaseService<{Domain}> {

    /** 列表查询,条件与 page 一致 */
    List<{Domain}> list({Domain}Query query);

    Page<{Domain}> page({Domain}Query query);

    /** ids 逗号分隔,如 "1,2,3" */
    boolean deleteByIds(String ids);
}
package com.tofly.wm.ledger.{domain};

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.yulichang.base.MPJBaseServiceImpl;
import com.tofly.mybatisplus.page.PageHelper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

@Service
public class {Domain}ServiceImpl extends MPJBaseServiceImpl<{Domain}Mapper, {Domain}>
        implements {Domain}Service {

    /**
     * 列表与分页共用条件
     */
    private LambdaQueryWrapper<{Domain}> buildWrapper({Domain}Query q) {
        return Wrappers.<{Domain}>lambdaQuery()
                //申请部门(Query 用 Long,Entity 字段为 OrganizationEntry)
                .eq(Objects.nonNull(q.getApplyDeptId()), {Domain}::getApplyDept, q.getApplyDeptId())
                //编码
                .like(StrUtil.isNotBlank(q.getCode()), {Domain}::getCode, q.getCode())
                .eq({Domain}::getDeleted, false);
    }

    @Override
    public List<{Domain}> list({Domain}Query query) {
        return this.list(buildWrapper(query));
    }

    @Override
    public Page<{Domain}> page({Domain}Query query) {
        return PageHelper.startPage(query, p -> this.list(query));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean deleteByIds(String ids) {
        if (StrUtil.isBlank(ids)) {
            return false;
        }
        List<Long> idList = Arrays.stream(ids.split(","))
                .map(String::trim)
                .filter(StrUtil::isNotBlank)
                .map(Long::parseLong)
                .collect(Collectors.toList());
        return this.removeByIds(idList);
    }
}

7) 台账 Controller(分页 + 导出,无写接口)

参考 ledger/arriveledger/verify

@Api(tags = "{台账中文名}")
@RestController
@RequestMapping("/{domain}Ledger")
@RequiredArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
public class {Domain}LedgerController {

    {Domain}LedgerService {domain}LedgerService;

    @GetMapping("/page")
    @ApiOperation("分页查询")
    public ResultResponse<Page<{Domain}LedgerVo>> page({Domain}LedgerQuery query) {
        return ResultResponse.success({domain}LedgerService.page(query));
    }

    @GetMapping("/export")
    @ApiOperation("导出")
    public void export({Domain}LedgerQuery query) {
        {domain}LedgerService.export(query);
    }
}

8) 台账 Service(MPJ 多表关联 + EasyPoi 导出)

@Service
@AllArgsConstructor
public class {Domain}LedgerService {

    SomeEntityService someEntityService;

    public List<{Domain}LedgerVo> getList({Domain}LedgerQuery query) {
        return someEntityService.selectJoinList({Domain}LedgerVo.class,
                MPJWrappers.<SomeEntity>lambdaJoin()
                        .selectAs(SomeEntity::getId, {Domain}LedgerVo::getId)
                        .selectAs(Related::getName, {Domain}LedgerVo::getRelatedName)
                        .innerJoin(Related.class, Related::getId, SomeEntity::getRelatedId)
                        .eq(SomeEntity::getDeleted, false)
                        .like(StrUtil.isNotBlank(query.getCode()), SomeEntity::getCode, query.getCode())
        );
    }

    public Page<{Domain}LedgerVo> page({Domain}LedgerQuery query) {
        return PageHelper.startPage(query, this::getList);
    }

    public void export({Domain}LedgerQuery query) {
        List<{Domain}LedgerVo> voList = this.getList(query);
        Map<String, Object> dataMap = HashMaps.of("dataList", voList);
        EasyPoiUtil.download("{台账中文名}.xlsx", "/template/{domain}Ledger.xlsx", dataMap);
    }
}

9) Mapper.xml(仅复杂 SQL 时使用)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.tofly.wm.ledger.{domain}.{Domain}Mapper">

    <select id="selectCustomPage" resultType="com.tofly.wm.ledger.{domain}.{Domain}Vo">
        SELECT t.ID, t.CODE, t.CREATE_TIME
        FROM TF_WM_{DOMAIN}_W t
        WHERE t.DELETED = 0
        <if test="qry.code != null and qry.code != ''">
            AND t.CODE LIKE '%' || #{qry.code} || '%'
        </if>
        ORDER BY t.CREATE_TIME DESC
    </select>
</mapper>

10) MeterConstant(字典目录)

package com.tofly.wm.cons;

public interface MeterConstant {
    String DICT_METER_JJCD = "METER_JJCD";       // 紧急程度
    String DICT_METER_TYPE = "METER_TYPE";       // 水表类型
    String DICT_METER_DIAMETER = "METER_DIAMETER"; // 口径
    String DICT_WORKER_ORDER = "METER_ORDER_TYPE"; // 工单类型
    String PURCHASE_APPLY = "PURCHASE_APPLY";    // 采购申请流水类型
}

11) 反例

// 错误:返回裸 ResultResponse
public ResultResponse page(MeterQuery query) { ... }

// 错误:在 Mapper 上用注解写 SQL
@Select("select * from TF_WM_METER_W")
List<Meter> listAll();

// 错误:Controller 内拼装 Wrappers / 调 Mapper
@GetMapping("/page")
public ResultResponse<Page<Meter>> page(MeterQuery query) {
    return ResultResponse.success(meterMapper.selectPage(...));
}

// 错误:提供单条删除(本仓库仅 DELETE /deleteByIds)
@DeleteMapping("/{id}")
public ResultResponse<Boolean> delete(@PathVariable Long id) { ... }

// 错误:与 meter 模块不一致却强行使用 PUT 修改(本仓库 ledger CRUD 用 POST /update)
@PutMapping
public ResultResponse<Boolean> update(@RequestBody Meter meter) { ... }

// 错误:list 与 page 使用不同 Query 或不同条件逻辑(应共用 buildWrapper / list(query))
@GetMapping("/list")
public ResultResponse<List<Meter>> list(MeterListQuery query) { ... }

// 错误:Entity 用 Long + String 拆部门,未用 OrganizationEntry
Long applyDeptId;
String applyDeptName;

// 错误:OrganizationEntry/UserNameEntry 再建 *_name 快照列
// apply_dept_id + apply_dept_name 双列落库

// 错误:DictEntry 映射 NUMBER 或非 VARCHAR 列
@TableField("PRIORITY")
Integer priorityCode;

// 错误:FileEntry 映射库列 CLOB/ATTACH
@TableField("ATTACH")
FileEntryList attach;

// 错误:FileEntry 未标注 exist = false
FileEntryList photos;

// 错误:Query 使用 OrganizationEntry(Query 应使用 Long applyDeptId 等)
OrganizationEntry applyDept;

// 错误:字典字段无 @DictDirectory 或魔法字符串
@TableField("PRIORITY_CODE")
String priorityCode;

仓库对照路径

类型 路径
标准 CRUD ledger/meter/MeterController.java
采购台账 ledger/purchase/PurchaseLedgerController.java
到货台账 ledger/arrive/MeterArriveLedgerController.java
落地检台账 ledger/verify/MeterVerifyLedgerController.java
附件 FileEntryList Entity 用 @TableField(exist = false),参考 manage/puachase/claim/PurchaseClaim.java