CRUD 正确姿势

创建 实体类

Student.java

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
@SuperBuilder
@Table(value = "t_student", comment = "学生表")
public class Student extends BaseEntity {

    String name;
}

DAO

StudentDAO.java

@Repository
public class StudentDAO extends EntityDAOImpl<Student, Long> {

}

Service

StudentService.java

@Service
public class StudentService extends BaseServiceImpl<StudentDAO, Student> {

    public StudentService(StudentDAO baseDAO) {
        super(baseDAO);
    }
}

Controller 类

StudentController.java

@Controller
@RequestMapping("students")
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
public class StudentController extends BaseFormController<Student, StudentService> {

    public StudentController(StudentService studentService) {
        super(studentService, "demos/student/edit");
    }
}

生成表

tableGenerator.createTable(Student.class);

生成 Report

  @Test
  public void testReport() {
      reportService.saveOrUpdate(Report.builder()
              .code("t_student")//  建议和数据库表名保持一致
              .tplName("demos/student/list") // 拷贝模版页面到指定目录
//                .tplName("tpl/list") // 没有特殊要求使用模版页面
//                .tplName("tpl/ajax_list") // 没有特殊要求使用模版页面
              .name("学生信息")
                      .additionalInfo(Params.builder(1).pv("operator-bar", true) // 显示操作按钮
                              .pv("endpoint", "students")
                              .build()) // 显示操作按钮
              .querySql("select id, name, create_time from t_student where name like :name and is_deleted = 0")
              .queryFieldList(Arrays.asList(
                      new QueryField("name", "姓名")
              ))
              .reportColumnList(Arrays.asList(
                      new HiddenReportColumn("id"),
                      new ReportColumn("name", "名称", true),
                      new ReportColumn("create_time", "创建时间", false,null, Arrays.asList("localDateTimeConverter"))
              ))
              .pageable(true)
              .sidx("create_time")
              .sord(SordEnum.DESC)
              .build());
  }

获取 report id = 858412707060166656

配置 index.html 菜单

<li class="nav-item">
    <a class="nav-link" href="javascript:addTab('student', '测试student', '/reports/858412707060166656')">
        <i class="nav-icon icon-home"></i> 测试student
    </a>
</li>

查看list页面

添加Form

拷贝 tpl/edit.html

$.ajax ({
   url: "/endpoint", // 修改一处,改成自己的 endpoint
         type: "post",
         data: JSON.stringify(formData),
         dataType: "json",
         contentType: "application/json; charset=utf-8",
         success: function(data){
               if (!data.success) {
                   toastr.error(data.message)
               } else {
                   idDOM.value = data.id
                   toastr.success("保存成功")
               }
           }
     });

查看edit页面

Entity 实体 字段映射

Entity ComplexModel.java

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
@SuperBuilder
@Table(value = "t_complex_model", comment = "测试")
public class ComplexModel extends BaseEntity {

    @NotBlank
    String name;

    /**
     * 查询字典表 sys_dict 中获取数据
     * 通过注解 @Sql 获取 label 值
     */
    @Embedded(columnPrefix="material_type_")
    @JsonAlias("materialType")
    @JsonDeserialize(using = EntityWithCodePropertyDeserializer.class)
    @Sql(value = "select name code, label from sys_dict WHERE type = 'MATERIAL_TYPE'  AND name = :name", params="id@materialType.code", nullWhenParamsIsNull="code")
    @DictType(type = "MATERIAL_TYPE") // 可以省略 从@Sql 获取label
    @DictValueCheck(type = "MATERIAL_TYPE")
    DictValue materialType;

    /**
     * DictUtils.fillDictLabel(this) 手动填充label
     */
    @Embedded(columnPrefix="unit_")
    @JsonAlias("unit")
    @JsonDeserialize(using = EntityWithCodePropertyDeserializer.class)
    @DictType(type = "UNIT")
    @DictValueCheck(type = "UNIT")
    DictValue unit;

    /**
     * 用于 BaseCodeEntity 的关联,拥有 id , 区别于DictValue
     */
//    @Embedded(columnPrefix="plant_")
//    @JsonAlias("plantCode")
//    @JsonDeserialize(using = EntityWithCodePropertyDeserializer.class)
//    @Sql(value = "SELECT id, code, description from core_plant WHERE id = :id", params="id@plant.id", nullWhenParamsIsNull="id")
//    private IdCodeValue plant;
    /**
     * 使用注解 @JsonFormat(shape = JsonFormat.Shape.OBJECT) 可以不使用 @JsonValue
     */
    CodeDescription.CategoryEnum category;

    @Column(comment = "状态")
    WorkStatusEnum workStatus;

    @Embedded(columnPrefix="category_")
    @JsonAlias("categoryDict")
    @JsonDeserialize(using = EntityWithCodePropertyDeserializer.class)
    @DictType(type = "CategoryEnum")
    @DictValueCheck(type = "CategoryEnum")
    DictValue categoryDict;

    @Column(comment = "年龄", nullable = false)
    private Integer age;

    @Column(comment = "出生时间")
    private LocalDate birthday;

    /**
     * 必须使用 @JsonValue, 如果使用 @JsonFormat 数据写入会有问题
     */
    @Column(comment = "分类", value = "category_list", nullable = false)
    private List<CodeDescription.CategoryEnum> categoryList;

    @Column(comment = "字典分类", value = "category_dict_list", nullable = false)
    @DictType(type = "CategoryEnum")
    private List<DictValue> categoryDictList;

    @Column(comment = "婚否", nullable = false)
    private Boolean marriage;

    @Column(comment = "附件", columnDefinition = "text", value = "attachment", nullable = false)
    private List<Map<String, Object>> attachmentList;

    @Column(comment = "学习经历", columnDefinition = "text", value = "school_experience")
    private List<List<String>> schoolExperienceList;

    @Column(columnDefinition = "text", value = "map", nullable = false)
    private Map<String, Object> map;

    EmbeddedValue embeddedValue;

    @ManyToOne(value = "packaging_group_id", parentTable = "core_group", comment = "包装组id")
    @JsonAlias("packagingGroupId")
    @JsonDeserialize(using = EntityWithLongIdPropertyDeserializer.class)
    private ObjectGroup packagingGroup;

    @Column(value = "packaging_group_code", comment = "包装组code")
    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
    private String packagingGroupCode;

    @ManyToMany(thirdPartyTable = "pur_order_type_item_config",
            referenceTable = "pur_item_config", referenceColumnName = "item_id", columnDefinition = "type_id")
    private List<ItemConfig> itemConfigList;

    @ManyToOne(value = "core_code_group_id", parentTable = "core_code_group")
    @JsonAlias("codeGroupId")
    @JsonDeserialize(using = EntityWithLongIdPropertyDeserializer.class)
    private CodeGroup codeGroup;

    @OneToMany(joinValue = "business_partner_id",
            subTable = "core_business_partner_role",
            cascadeInsertOrUpdate = true, reversePropertyName = "businessPartnerId", cascadeDelete = false)
    private List<Role> roleList;

    @Select(table = "mm_classification", joinValue = "material_id", referencePropertyName = "materialId")
    private List<Classification> classificationList;

    @Select(table = "core_code_assign", joinValue = "core_code_group_id", referencePropertyName = "id", oneToOne= true)
    private CodeAssign codeAssign;

    @Sql(value = "select * from t_person where id = :id AND name like :title", params = "id@person.id, title@title")
    private Person p3;

    @AllArgsConstructor
    @Getter
    @JsonFormat(shape = JsonFormat.Shape.OBJECT)
    public enum WorkStatusEnum {
        UNFINISHED(0, "未完成"),
        FINISHED(1, "已完成");
        private static final Map<Integer, WorkStatusEnum> codeMap = new HashMap<>();

        static {
            for (WorkStatusEnum e : values()) {
                codeMap.put(e.code, e);
            }
        }

        private final int code;
        private final String label;

        public int getCode() {
            return this.code;
        }

        /**
         * code枚举 必须重写toString()
         *
         * @return
         */
        @Override
        public String toString() {
            return String.valueOf(code);
        }

        public static WorkStatusEnum valueOfCode(int code) {
            return codeMap.get(code);
        }
    }
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class EmbeddedValue implements JsonStringToObjectConverterFactory.JsonValue {

    @DictType(type = "MATERIAL_TYPE") // 可以省略 从@Sql 获取label
    DictValue dictValue;

    String text;

}

value value.json

{
        "name": "Rick.Xu",
        "materialType": "HIBE",
        "unit": "EA",
        "category": "MATERIAL",
        "status": "0",
        "categoryDict": "SALES_ORG",
        "age": 34,
        "birthday": "2021-12-26",
        "categoryList": [
            "MATERIAL",
            "PURCHASING_ORG"
        ],
        "categoryDictList": [
            "MATERIAL",
            "PURCHASING_ORG"
        ],
        "marriage": true,
        "attachmentList": [
            {
                "name": "picture",
                "url": "baidu.com"
            }
        ],
        "schoolExperienceList": [
            [
                "2023-11-11",
                "苏州大学"
            ],
            [
                "2019-11-11",
                "苏州中学"
            ]
        ],
        "map": {
            "name": "picture",
            "url": "baidu.com"
        },
        "embeddedValue": {
            "dictValue": {
                "code": "HIBE",
                "label": "耗材用品"
            },
            "text": "text"
        }
}

response Json Value

{
    "success": true,
    "code": 200,
    "message": "OK",
    "data": {
        "id": "856929212655734784",
        "createBy": "1",
        "createTime": "2024-08-19 14:23:43",
        "updateBy": "1",
        "updateTime": "2024-08-19 14:23:43",
        "name": "Rick.Xu",
        "materialType": {
            "code": "HIBE",
            "label": "耗材用品"
        },
        "unit": {
            "code": "EA",
            "label": "个"
        },
        "category": "MATERIAL",
        "categoryDict": {
            "code": "SALES_ORG"
        },
        "age": 34,
        "birthday": "2021-12-26",
        "categoryList": [
            "MATERIAL",
            "PURCHASING_ORG"
        ],
        "categoryDictList": [
            {
                "code": "MATERIAL"
            },
            {
                "code": "PURCHASING_ORG"
            }
        ],
        "marriage": true,
        "attachmentList": [
            {
                "name": "picture",
                "url": "baidu.com"
            }
        ],
        "schoolExperienceList": [
            [
                "2023-11-11",
                "苏州大学"
            ],
            [
                "2019-11-11",
                "苏州中学"
            ]
        ],
        "map": {
            "name": "picture",
            "url": "baidu.com"
        },
        "embeddedValue": {
            "dictValue": {
                "code": "HIBE",
                "label": "耗材用品"
            },
            "text": "text"
        }
    }
}

安装 elasticsearch kibana analysis-ik

安装 elasticsearch

创建网络

docker network create es-network

启动容器 elasticsearch

docker run -d --name elasticsearch --net es-network  -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" elasticsearch:8.12.2

无法访问: http://localhost:9200/

修改配置文件 elasticsearch.yml, 位置在 /usr/share/elasticsearch/config/elasticsearch.yml

cluster.name: "docker-cluster"
network.host: 0.0.0.0

#----------------------- BEGIN SECURITY AUTO CONFIGURATION -----------------------
#
# The following settings, TLS certificates, and keys have been automatically      
# generated to configure Elasticsearch security features on 27-03-2024 23:44:30
#
# --------------------------------------------------------------------------------

# Enable security features
xpack.security.enabled: false

xpack.security.enrollment.enabled: true

# Enable encryption for HTTP API client connections, such as Kibana, Logstash, and Agents
xpack.security.http.ssl:
  enabled: false
  keystore.path: certs/http.p12

# Enable encryption and mutual authentication between cluster nodes
xpack.security.transport.ssl:
  enabled: true
  verification_mode: certificate
  keystore.path: certs/transport.p12
  truststore.path: certs/transport.p12
#----------------------- END SECURITY AUTO CONFIGURATION -------------------------

访问: http://localhost:9200/, 正常返回

{
  "name" : "c58e6cb0f4d8",
  "cluster_name" : "docker-cluster",
  "cluster_uuid" : "1JXFArBRRd6W9NatoEPajQ",
  "version" : {
    "number" : "8.12.2",
    "build_flavor" : "default",
    "build_type" : "docker",
    "build_hash" : "48a287ab9497e852de30327444b0809e55d46466",
    "build_date" : "2024-02-19T10:04:32.774273190Z",
    "build_snapshot" : false,
    "lucene_version" : "9.9.2",
    "minimum_wire_compatibility_version" : "7.17.0",
    "minimum_index_compatibility_version" : "7.0.0"
  },
  "tagline" : "You Know, for Search"
}

安装 kibana

docker run -d --name kibana -e ELASTICSEARCH_HOSTS=http://elasticsearch:9200 --net es-network -p 5601:5601 kibana:8.12.2

访问: localhost:5601

安装ik

下载对应的版本: https://github.com/infinilabs/analysis-ik
修改权限

chmod -R 777 /Users/rick/Downloads/elasticsearch-analysis-ik-8.12.2/

拷贝到目录 /usr/share/elasticsearch/plugins
执行命令 elasticsearch-plugin list 将会显示

elasticsearch-analysis-ik-8.12.2

重启 elasticsearch
在kibana执行

POST _analyze
{
  "analyzer": "ik_smart",
  "text": "我是中国人, 我的家乡在江苏"
}

如果没有改ik的权限, 执行 elasticsearch-plugin list

Exception in thread "main" java.lang.IllegalStateException: Plugin [opensearch-analysis-ik-2.12.0] is missing a descriptor properties file.
        at org.elasticsearch.plugins.PluginDescriptor.readFromProperties(PluginDescriptor.java:233)
        at org.elasticsearch.plugins.cli.ListPluginsCommand.printPlugin(ListPluginsCommand.java:63)
        at org.elasticsearch.plugins.cli.ListPluginsCommand.execute(ListPluginsCommand.java:57)
        at org.elasticsearch.common.cli.EnvironmentAwareCommand.execute(EnvironmentAwareCommand.java:54)
        at org.elasticsearch.cli.Command.mainWithoutErrorHandling(Command.java:85)
        at org.elasticsearch.cli.MultiCommand.execute(MultiCommand.java:94)
        at org.elasticsearch.cli.Command.mainWithoutErrorHandling(Command.java:85)
        at org.elasticsearch.cli.Command.main(Command.java:50)
        at org.elasticsearch.launcher.CliToolLauncher.main(CliToolLauncher.java:64)

sharp-excel使用指北:从模版文件中导出内容(三)

我有个采购订单模版, 通过数据选渲染模版,然后导出. 模版有些cell是需要替换文字, 有些需要自动插入行, 印章(图片)的插入.

模版文件:

http://xhope.top/wp-content/uploads/2023/08/tpl.png

代码示例子:

@Test
    public void testWriteFromTemplate() throws IOException {
         String template = "/Users/rick/Space/Workspace/sharp-admin/src/main/resources/templates/excel/po.xlsx";
        String dist = "/Users/rick/Space/Workspace/sharp-admin/src/main/resources/templates/excel/dist.xlsx";

        // 这样就不会改变模版文件
        byte[] bytes = IOUtils.toByteArray(new FileInputStream(template));

        ExcelWriter excelWriter = new ExcelWriter(new XSSFWorkbook(new ByteArrayInputStream(bytes)));

        excelWriter.writeCell(new ExcelCell(7, 3, "PO NO: PY20230726-100"));

        excelWriter.writeCell(new ExcelCell(1, 5, "供方(Vendor):苏州XX工程有限公司"));
        excelWriter.writeCell(new ExcelCell(2, 6, "李总 18898876623"));
        excelWriter.writeCell(new ExcelCell(2, 7, "0512-88359511"));
        excelWriter.writeCell(new ExcelCell(2, 8, "0512-88359511"));
        excelWriter.writeCell(new ExcelCell(1, 9, "ADD:  江苏省南京市武侯区"));


//        excelWriter.writeCell(new ExcelCell(7, 5, "需方:XX电机制造(苏州)有限公司"));
//        excelWriter.writeCell(new ExcelCell(7, 6, "慧Xx 18898876623"));
//        excelWriter.writeCell(new ExcelCell(7, 7, "0512-77359511"));
//        excelWriter.writeCell(new ExcelCell(7, 8, "0512-77359511"));
//        excelWriter.writeCell(new ExcelCell(7, 10, "交货地点:苏州市高新区"));


        ArrayList<Object[]> data = new ArrayList<>();
        data.add(new Object[]{1, "资材编号1", "品 名", "型号规格", 3, "单位", 1, 11, "2022-11-16"});
        data.add(new Object[]{2, "资材编号2", "品 名", "型号规格", 3, "单位", 1, 12, "2022-11-16"});
        data.add(new Object[]{3, "资材编号3", "品 名", "型号规格", 3, "单位", 1, 11, "2022-11-16"});
        data.add(new Object[]{4, "资材编号4", "品 名", "型号规格", 3, "单位", 1, 12, "2022-11-16"});

        int rowSize = data.size();

        // 获取 cell 样式
        XSSFRow row = excelWriter.getActiveSheet().getRow(11);
        int physicalNumberOfCells = row.getPhysicalNumberOfCells();
        XSSFCellStyle[] cellStyles = new XSSFCellStyle[physicalNumberOfCells];
        for (int i = 0; i < physicalNumberOfCells; i++) {
            cellStyles[i] = row.getCell(i).getCellStyle();
        }

        XSSFColor redColor = new XSSFColor(Color.RED, new DefaultIndexedColorMap());

        // 文字色
        XSSFFont font = excelWriter.getBook().createFont();
        font.setColor(redColor);
        font.setBold(true);

        excelWriter.insertAndWriteRow(1, 12, data, row.getHeightInPoints(), cellStyles, (ecell, cell) -> {
            if (ecell.getX() == 9 && !String.valueOf(ecell.getValue()).matches("\\d{4}-\\d{2}-\\d{2}")) {
                XSSFCellStyle newStyle = cell.getCellStyle().copy();
                // 如果非日期类型, 表示是备注,则用红色加粗显示. CellStyle是copy的, 但是 XSSFFont 需要创建, 然后再赋值. 不然是引用, 会影响其他的cell
                font.setFamily(newStyle.getFont().getFamily());
                font.setFontName(newStyle.getFont().getFontName());
                font.setFontHeight(newStyle.getFont().getFontHeight());
                newStyle.setFont(font);
                cell.setCellStyle(newStyle);
            }
        });

        excelWriter.writeCell(new ExcelCell(3, 13 + rowSize, "¥46.00"));
        excelWriter.writeCell(new ExcelCell(3, 14 + rowSize, "RMB46"));
        excelWriter.writeCell(new ExcelCell(2, 15 + rowSize, "哈哈"));
        excelWriter.writeCell(new ExcelCell(4, 28 + rowSize, "本公司确认:王总2023/08/10"));

        excelWriter.getBook().setSheetName(0, "20230810");

        // 插入图片
        //创建一个excel文件,名称为:
        XSSFWorkbook workbook = excelWriter.getBook();
        //创建一个sheet,名称为工作簿1
        XSSFSheet sheet = excelWriter.getActiveSheet();

        BufferedImage bufferImg;
        //先把读进来的图片放到一个ByteArrayOutputStream中,以便产生ByteArray
        ByteArrayOutputStream byteArrayOut = new ByteArrayOutputStream();
        String imageUrl = "/Users/rick/Space/Share/seal.png";

        //获取图片后缀
        bufferImg = ImageIO.read(new File(imageUrl));
        ImageIO.write(bufferImg, "png", byteArrayOut);

        //画图的顶级管理器,一个sheet只能获取一个(一定要注意这点)
        XSSFDrawing patriarch = sheet.createDrawingPatriarch();
        //anchor主要用于设置图片的属性
        XSSFClientAnchor anchor = new XSSFClientAnchor(0, 0, 0, 0, (short) 7, 19 + rowSize, (short) 9, 30 + rowSize);
        anchor.setAnchorType(ClientAnchor.AnchorType.MOVE_AND_RESIZE);
        //插入图片
        patriarch.createPicture(anchor, workbook.addPicture(byteArrayOut.toByteArray(), XSSFWorkbook.PICTURE_TYPE_PNG));

        excelWriter.toFile(new FileOutputStream(dist));
    }

sharp-database 使用注解 @Sql 自定义查询

sharp-database 使用注解 @Sql 自定义查询

实体类中添加注解 @Sql

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@SuperBuilder
@Table(value = "t_book", comment = "书")
public class Book extends BaseEntity {

    private String title;

    /**
     * 书的拥有者
     */
    @ParamName({"person_id", "personId"})
    @JsonAlias("personId")
    @JsonDeserialize(using = EntityWithLongIdPropertyDeserializer.class)
    @ManyToOne(value = "person_id", parentTable = "t_person")
    private Person person;

    @Sql("select * from t_person")
    private List<Person> allPerson;

    @Sql("select * from t_person where id = 552098712424472576")
    private Person p1;

    @Sql(value = "select * from t_person where id = :id", params = "id@person.id")
    private Person p2;

    @Sql(value = "select * from t_person where id = :id AND name like :title", params = "id@person.id, title@title")
    private Person p3;

}
public @interface Sql {

    String value();

    String params() default "";

    /**
     * 参数为null的时候, 直接返回结果null,无需数据库查询
     * @return
     */
    String[] nullWhenParamsIsNull() default {};

}

value: sql 语句
params: sql 参数. 多个参数用 , 隔开; 每个参数 参数名@参数值. 参数值可以是静态的,也可以是来自对象的属性值
nullWhenParamsIsNull: 参数为null的时候, 直接返回结果null,无需数据库查询

Bean类中添加注解 @Sql

BookQuery.java 虽然标记 @Table 但是不是实体对象

@Data
@Table
public class BookQuery {

    private Long id;

    private String title;

    private String personId;

    @Sql(value = "select * from t_person where id = :personId", params = "personId@personId")
    private Person person;

    /**
     * 标签
     */
    @Sql(value = "select t_tag.* from t_book, t_tag, t_book_tag where t_book_tag.book_id = t_book.id AND t_book_tag.tag_id = t_tag.id AND t_book.id = :id", params = "id@id")
    private List<Tag> tagList;

    @Sql("select * from t_person")
    private List<Person> allPerson;

    @Sql("select * from t_person where id = 552098712424472576")
    private Person p1;

    @Sql(value = "select * from t_person where id = :id", params = "id@person.id")
    private Person p2;

    @Sql(value = "select * from t_person where id = :id AND name like :title", params = "id@person.id, title@title")
    private Person p3;

    @Sql(value = "select * from t_person where id = :id", params = "id@person.id")
    Map<String, Object> personMap;
}

测试,借助对象 EntityDAOSupport 进行查询

    @Autowired
    private EntityDAOSupport entityDAOSupport;

    @Test
    public void testBySql() {
        List<BookQuery> query = entityDAOSupport.query("select * from t_book where id = 617342584087388160", null, BookQuery.class);
        System.out.println(query);

        Optional<BookQuery> optional = entityDAOSupport.queryForObject("select * from t_book where id = 617342584087388160", null, BookQuery.class);
        System.out.println(optional.get());
    }

    @Test
    public void testByInstance() {
        BookQuery bookQuery = new BookQuery();
        bookQuery.setId(617342584087388160L);
        bookQuery.setPersonId("552100575806939136");

        entityDAOSupport.query(bookQuery);
        System.out.println(bookQuery);
    }