Linux自动打包上传并运行SpringBoot程序

SSH 免密登录

生成公钥、私钥

ssh-keygen

发送公钥

ssh-copy-id -i /Users/rick/.ssh/id_rsa.pub root@172.31.1.2

配置别名

.ssh/config

Host xhope
  HostName 172.31.1.2
  User root
  IdentityFile ~/.ssh/id_rsa

Host dao
  HostName 172.31.1.3
  User root
  IdentityFile ~/.ssh/id_rsa

登录

ssh xhope

程序执行脚本

#!/bin/bash

cd /usr/local/projects/dao
PID=`ps -ef | pgrep -f "8082"`
if [  -n "$PID" ]
then
        kill -9 $PID
fi

rm -f product-manager-1.0-SNAPSHOT.jar
cp deploy/product-manager-1.0-SNAPSHOT.jar .

nohup /usr/local/jdk1.8.0_65/bin/java -Xms1024m -Xmx2048m -Dspring.profiles.active=prod -jar product-manager-1.0-SNAPSHOT.jar --server.port=8082 &

exit

mvn.sh

#!/bin/bash

mvn clean package -Dmaven.test.skip=true
scp /Users/rick/Space/Workspace/product-manager/target/product-manager-1.0-SNAPSHOT.jar root@172.31.1.2:/usr/local/projects/dao/deploy

ssh dao "bash -s < /usr/local/projects/dao/clean.sh"

执行 mvn.sh 后,自动打包上传并运行程序

sharp-generator

上篇文章《CRUD 正确姿势》步骤比较繁琐,可以通过 sharp-generator 生成固定代码

 @Test
 public void testGenerator() throws IOException {
     SQLUtils.execute("Drop TABLE t_student");

     generator.execute(Student.class,
             "/Users/rick/Space/Workspace/sharp-project/sharp-admin/src/main/java/com/rick/admin/module/student",
             Params.builder()
                     .pv(Generator.CONTROLLER, true) // 是否创建 controller
                     .pv(Generator.FORM_PAGE, "demos/student/edit") // 编辑页面路径

                     .pv(Generator.REPORT, true) // 是否创建 report
                     .pv(Generator.REPORT_TEST_PATH, "/Users/rick/Space/Workspace/sharp-project/sharp-admin/src/test/java/com/rick/admin/demo")
                     .pv(Generator.REPORT_TEST_PACKAGE, "com.rick.admin.demo")
                     .build());
 }

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)