# 	基于LDP框架的应用开发文档

## 一、简介

此文档介绍基于LDP框架的应用开发。LDP框架已封装好各种依赖库、工具包，应用开发者只需聚焦在具体业务实现，快速构建出可独立运行的应用。通过参考样例工程，免去复杂的配置，开发者可以快速搭建一个基于LDP框架的应用。

## 二、开发环境

### 2.1. 开发工具与运行环境

技术依赖：SpringBoot、Dubbo、Nacos、LDP框架服务，LDP框架API组件jar等。

构建技术：Maven 3.6+

JAVA版本：JRE1.8+

数据库：MySQL

IDE: IDEA（推荐）、Eclipse

### 2.2. 远程服务介绍及地址

#### 2.2.1 私服仓库地址

```
http://repo.dev.shxrtech.com/repository/sinra
```

备注：主要用于发布LDP框架依赖包、以及加快其它依赖库下载速度

#### 2.2.2 配置本地hosts服务地址

以下服务是项目运行必要的服务，请根据实际情况在本地hosts文件中配置。

Nacos服务地址：nacos-server0

Mysql服务地址：mysql-server0

Redis服务地址： redis-server0

LDP服务地址：ldp-server0

内部开发环境配置（仅供参考）：

```txt
# mysql 数据库服务
47.103.46.246 mysql-server0
# redis 服务
47.103.46.246 redis-server0
# nacos 服务
47.103.46.246 nacos-server0
# ldp服务
47.103.46.246 ldp-server0
```

#### 2.2.3 YAPI接口文档地址

包含已有接口的API文档

 http://api.dev.shxrtech.com/ 

### 三、工程开发命名规范

下述命名规范中，[projectname]为项目名，[modulename]为模块名

### 3.1. 主工程命名

ldp-app-[projectname]

### 3.2. 子模块命名

实体模块，主要存放实体类

实体模块：[projectname]-api

业务模块：[projectname]-[modulename]

启动模块：[projectname]-startup

### 3.3. JAVA开发命名

基础包名：com.sinra.ldp

实体包名：com.sinra.ldp.model.[projectname].[modulename]

视图层包名：com.sinra.ldp.[projectname].[modulename].rest

服务层包名：com.sinra.ldp.[projectname].[modulename].service

视图层类名：com.sinra.ldp.[projectname].[modulename].rest.XXXRest.java

服务层类名：com.sinra.ldp.[projectname].[modulename].service.XXXService.java

实现层类名：com.sinra.ldp.[projectname].[modulename].service.impl.XXXServiceImpl.java

启动类名：com.sinra.ldp.[projectname].Ldp[projectname]Application.java

### 3.4. jar包输出命名

[projectname]-startup-[version].jar

### 3.5. 服务命名规范

[projectname]-service

### 3.6 视图层接口地址命名规范

为了更方便记录日志，所以推荐使用GET、POST两种方式，并在URL中体现出操作方式。

在Rest类加上注解：

```java
@RequestMapping("/[modulename]")
```

方法注解：

```java
//新增方法请求地址
@PostMapping("/add")

//查询单条数据请求地址
@GetMapping("/get/{id}")

//查询列表数据
@GetMapping("/get/list")

//查询分页数据
@GetMapping("/get/page")

//修改单条数据
@PostMapping("/mod/{id}")

//删除单条数据
@PostMapping("/del/{id}")

//批量增删改查
@PostMapping("/batch")
```

## 四、样例工程说明

### 4.1. 样例工程结构图

![样例工程结构图](./imgs/example-project.png)

### 4.2. 结构图说明

1. 样例工程名称
2. api模块（主要包含实体类）
3. biz子模块（主要是业务接口）
4. startup子模块（主要包含启动类、构建可运行jar包）

### 4.3. 样例工程模块

#### 4.3.1 example-api模块

example-api模块，主要包含业务功能会用到的实体类，并且在开发完成后，需要先将example-api模块的jar发布到基础服务中。数据库接口调用是远程调用，如果实体类在本地，基础服务就无法执行正确的查询和序列化，所以需要先将实体类上传。**上传后基础服务会重启，所以服务会中断一会儿**。

下面是example-api模块的**pom.xml**

```xml
<build>
    <plugins>
        <plugin>
            <groupId>com.sinra.ldp</groupId>
            <artifactId>ldp-maven-plugin</artifactId>
            <version>1.0-SNAPSHOT</version>
            <configuration>
                <file>target/example-api-1.0-SNAPSHOT.jar</file>
                <url>http://ldp-server0:8500/restart</url>
                <env>dev</env>
            </configuration>
        </plugin>
    </plugins>
</build>
```

上传流程为：

```shell
#在根目录执行编译
mvn clean install -U
#切换到example-api目录
cd example-api
#执行上传
mvn ldp:upload
```

#### 4.3.2 example-biz模块

example-biz模块，主要作为业务接口开发模块，包含视图层、服务层、sql语句xml文件等相关信息。example-biz需要依赖example-api模块，并在此模块中实现业务开发。

#### 4.3.3 example-startup模块

example-startup作为example工程启动模块、打包模块，主要包含启动类以及各种配置。执行打包命令

```shell
mvn clean install -U
```

会在target目录生成**examp-startup-1.0-SNAPSHOT.jar**，执行下面命令可以启动example：

```shell
java -jar examp-startup-1.0-SNAPSHOT.jar
```

访问： http://localhost:8800/example/get/page?pageIndex=1&pageSize=5 即可看到分页数据

### 4.4. 服务配置说明

#### 4.4.1 POM文件Maven私服仓库配置

在工程pom.xml中配置

```xml
<repositories>
    <repository>
        <id>sinra-repo</id>
        <url>http://repo.dev.shxrtech.com/repository/sinra</url>
        <releases>
            <enabled>true</enabled>
        </releases>
        <snapshots>
            <enabled>true</enabled>
        </snapshots>
    </repository>
</repositories>

<pluginRepositories>
    <pluginRepository>
        <id>sinra-repo</id>
        <url>http://repo.dev.shxrtech.com/repository/sinra</url>
        <releases>
            <enabled>true</enabled>
        </releases>
    </pluginRepository>
</pluginRepositories>

<distributionManagement>
    <repository>
        <id>ldp-release</id>
        <url>http://repo.dev.shxrtech.com/repository/maven-releases/</url>
    </repository>
    <snapshotRepository>
        <id>ldp-snapshot</id>
        <url>http://repo.dev.shxrtech.com/repository/maven-snapshots/</url>
    </snapshotRepository>
</distributionManagement>
```

#### 4.4.2 POM文件Plugins 配置

工程pom.xml文件：

```xml
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>${surefire.version}</version>
            <configuration>
                <!--是否跳过单元测试-->
                <skipTests>true</skipTests>
            </configuration>
        </plugin>

        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>${maven-compiler.version}</version>
            <configuration>
                <source>${java.version}</source>
                <target>${java.version}</target>
                <encoding>${project.build.sourceEncoding}</encoding>
            </configuration>
        </plugin>
    </plugins>
</build>
```

实体类模块（例如：example-api）pom.xml文件，主要是为了上传实体类模块包。

```xml
<build>
    <plugins>
        <plugin>
            <groupId>com.sinra.ldp</groupId>
            <artifactId>ldp-maven-plugin</artifactId>
            <version>1.0-SNAPSHOT</version>
            <configuration>
                <file>target/example-api-1.0-SNAPSHOT.jar</file>
                <url>http://ldp-server0:8500/restart</url>
                <env>dev</env>
            </configuration>
        </plugin>
    </plugins>
</build>
```

启动模块（例如：example-startup）pom.xml文件：

```xml
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <version>${spring-boot-maven.version}</version>
            <executions>
                <execution>
                    <goals>
                        <goal>repackage</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>
```

#### 4.4.3  POM文件LDP框架包依赖配置

```xml
<!-- 业务公共依赖库，包含自动填充注解、基础Dao、Rest参数注解等通用工具 -->
<dependency>
    <groupId>com.sinra.ldp</groupId>
    <artifactId>mcs-common</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>
<!-- rest依赖库，主要包含返回结果包装以及异常处理 -->
<dependency>
    <groupId>com.sinra.ldp</groupId>
    <artifactId>common-rest</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>
<!-- query依赖库，主要包含sql语句xml解析，绑定等 -->
<dependency>
    <groupId>com.sinra.ldp</groupId>
    <artifactId>common-query</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>
<!-- cache依赖库，主要包含缓存注解，及缓存处理逻辑 -->
<dependency>
    <groupId>com.sinra.ldp</groupId>
    <artifactId>common-cache</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

<!-- log依赖库，主要系统日志sql -->
<dependency>
    <groupId>com.sinra.ldp</groupId>
     <artifactId>common-log</artifactId>
     <version>1.0-SNAPSHOT</version>
 </dependency>

```

#### 4.4.4 YML服务名和端口配置

参考example-startup模块中resource/bootstrap.yml中**server.port**定义端口，这里为8800

**spring.profiles.active**决定服务启动的时候启用什么配置文件（bootstrap-dev.yml、bootstrap-local.yml），这里设置为**nacos**，所以启用**bootstrap-nacos.yml**文件配置。

```yml
server:
  # 服务端口
  port: 8800
nacos:
  # nacos地址
  server: nacos-server0:8018

spring:
  profiles:
    # 默认启用nacos配置
    active: nacos
  application:
    #服务名
    name: example-service
  messages:
    encoding: UTF-8
  cloud:
    nacos:
      server-addr: ${nacos.server}
      config:
        server-addr: ${nacos.server}
      discovery:
        server-addr: ${nacos.server}


```

#### 4.4.5 YML文件Dubbo服务配置

参考**bootstrap-nacos.yml**

```yml
dubbo:
  registry:
    address: nacos://${spring.cloud.nacos.server-addr}
  application:
    ##日志适配
    logger: slf4j
    ##输出访问日志
  protocol:
    accesslog: true
    serialization: java

  consumer:
    application: ${spring.application.name}-consumer
    parameters:
      protocol: dubbo
      serialization: java
      version: 1.0
      timeout: 15000
      group:  ${spring.cloud.nacos.discovery.group}
```

#### 4.4.6 YML文件注册中心Nacos配置

参考**bootstrap-nacos.yml**

```yml
spring:
  cloud:
    # nacos 配置
    nacos:
      discovery:
        register-enabled: true
        group: nacos_default_group
        # 服务消费者和提供者在不同网段，且本服务作为提供者时，配置为提供者实际部署机器名地址，并在消费者hosts文件中配置机器名与ip的映射
        #ip: ldp-server0
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
```

#### 4.4.7 YML文件LDP缓存配置

参考**bootstrap-nacos.yml**

```yml
ldp:
  query:
    location: query/*.xml
    dialect: mysql
  cache:
  	## redis配置
    redis:
      host: redis-server0
      port: 7268
      password: cacheForldp2020
      database: 1
      sentinel:
        master: ldp-master
        nodes:
          - redis-server0:6380
          - redis-server0:6381
          - redis-server0:6382
    instances:
      #用户会话级缓存
      session: 3600
      #应用级缓存默认不过期
      application: -1
  nacos:
      group: DEFAULT_GROUP
      requestTimeout: 5000
```

#### 4.4.8 logback.xml日志配置

参考**logback.xml**

```xml
<?xml version="1.0" encoding="UTF-8"?>
    <configuration>

        <property name="LOG_FILE" value="ldp-base-server" />

        <include resource="org/springframework/boot/logging/logback/defaults.xml" />
        <include resource="org/springframework/boot/logging/logback/console-appender.xml" />
        <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <File>${user.home}/.sinra/${LOG_FILE}.log</File>
            <encoder>
                <pattern>%date [%level] [%thread] %logger{60} [%file : %line] %msg%n</pattern>
            </encoder>
            <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">

                <!-- 按文件名2018-08-29-15[0].log，如果文件大小超过100kb,则继续分割日志2018-08-29-15-32[1].log,
       2018-08-29-15-32[2].log。%d和%i不能缺少 -->
                <fileNamePattern>${user.home}/.sinra/${LOG_FILE}.%d{yyyy-MM-dd.HH}_%i.log</fileNamePattern>
                <maxFileSize>10MB</maxFileSize>
                <maxHistory>30</maxHistory>
                <totalSizeCap>1GB</totalSizeCap>
                <!-- 保留1G日志 -->
            </rollingPolicy>
        </appender>

        <root level="INFO">
            <appender-ref ref="CONSOLE"/>
            <appender-ref ref="FILE"/>
        </root>
    </configuration>

```

## 五、数据接口调用介绍

### 5.1. 基础服务数据库调用示例及介绍

#### 5.1.2 Service介绍

1. service继承**BaseService**，并在<>传入对应的实体类，**BaseService**中是常用的增删改查接口。

![服务接口](./imgs/example-service.png)

2. 在实现类中继承**AbsBaseDao**，并实现service接口。

   ![实现类](./imgs/example-service-impl.png)

3. **AbsBaseDao**中使用**dubbo**远程依赖**Hibernate**接口**genericDaoService**，以及**JDBC**接口**jdbcDaoService**，所以实现类中可以直接调用。

   

#### 5.1.3 Hibernate 调用示例及介绍

Hibernate 接口可以使用实体类进行增删改查，也可以使用sql，这里先介绍使用实体类的操作，使用sql的方式与JDBC类似，下一小节介绍。

1. 新增数据

   ```java
   // AutoService注解在新增时会对实体类中配置了@AutoComputed字段根据规则进行填充
   //新增单条数据
   @Override
   @AutoService
   public void add(ExampleUserInfo exampleUserInfo) {
       genericDaoService.insert(exampleUserInfo);
   }
   
   //新增列表数据
   @Override
   @AutoService
   public void add(List<ExampleUserInfo> list) {
       genericDaoService.insertList(list);
   }
   ```

2. 查询数据

   ```java
   //查询单条数据
   @Override
   public ExampleUserInfo find(String s) {
       return (ExampleUserInfo) genericDaoService.findById(ExampleUserInfo.class, s);
   }
   
   //查询列表数据
   @Override
   public List<ExampleUserInfo> findList(Map<String, Object> param) {
       return genericDaoService.findByHql(ExampleUserInfo.class, param);
   }
   
   //查询分页数据
   @Override
   public Pagination<ExampleUserInfo> findPage(Pagination page, Map<String, Object> map) {
       return genericDaoService.findPageByHql(ExampleUserInfo.class, map, page.getPageIndex(), page.getPageSize());
   }
   
   ```

3. 更新数据

   ```java
   // AutoService注解在更新时会对实体类中updateId、updateTime、sortName进行自动填充
   // 根据实体类更新指定数据
   @Override
   @AutoService(init = false)
   public void update(ExampleUserInfo exampleUserInfo) {
       genericDaoService.update(exampleUserInfo);
   }
   
   // 更新列表数据
   @Override
   @AutoService(init = false)
   public void update(List<ExampleUserInfo> list) {
       genericDaoService.update(list);
   }
   
   // 按条件更新指定字段
   @Override
   public void update(Class<ExampleUserInfo> aClass, Map<String, Object> updateFileds, Map<String, Object> whereParam) {
       genericDaoService.update(aClass, updateFileds, whereParam);
   }
   ```

4. 删除数据

   ```java
   // 删除单条数据(实体类id不能为空)
   @Override
   public void delete(ExampleUserInfo exampleUserInfo) {
       genericDaoService.delete(exampleUserInfo);
   }
   
   // 删除列表数据
   @Override
   public void delete(List<ExampleUserInfo> list) {
       List<String> idList = list.stream().map(ExampleUserInfo::getId).collect(Collectors.toList());
       Map<String, Object> param = new HashMap<>(1);
       param.put("id", idList);
       genericDaoService.deleteList(ExampleUserInfo.class, param);
   }
   ```
   
5. 更多接口

   <details>
       <summary>展开</summary>
       <pre><blockcode> 
       /**
        * 新增
        *
        * @param o entity 对象
        */
       void insert(T o);
       /**
        * 批量新增
        *
        * @param list 列表数据
        */
       void insertList(List<T> list);
       /**
        * hibernate merge操作
        *
        * @param o entity 对象
        */
       void merge(T o);
       /**
        * merge批量操作
        *
        * @param list 列表
        */
       void merge(List<T> list);
       /**
        * 更新
        *
        * @param o entity 对象
        */
       void update(T o);
       /**
        * 动态更新，未设置的属性不做更新,默认以主键为查询条件
        *
        * @param o entity 对象
        */
       void dynamicUpdate(T o);
       /**
        * 批量动态更新
        *
        * @param list 列表
        */
       void dynamicUpdate(List<T> list);
       /**
        * 动态更新，设置属性为查询条件
        *
        * @param o
        * @param whereParam 自定义查询条件 ，map中key必须为entity存在字段
        */
       int dynamicUpdate(T o, Map<String, Object> whereParam);
       /**
        * 批量更新
        *
        * @param list 列表
        */
       void update(List<T> list);
       /**
        * 批量更新
        *
        * @param clazz        orm对象
        * @param updateFields 待更新字段集
        * @param whereParam   更新条件集
        *                     field
        * @return
        */
       int update(Class<T> clazz, Map<String, Object> updateFields, Map<String, Object> whereParam);
       /**
        * 删除
        *
        * @param o entity 对象
        */
       void delete(T o);
       /**
        * 批量删除
        *
        * @param clazz      需要修改的实体类
        * @param whereParam key：字段名 value：字段值，根据字段条件删除记录
        */
       int deleteList(Class<T> clazz, Map<String, Object> whereParam);
       /**
        * 删除
        *
        * @param clazz entity 类型
        * @param id    唯一标识
        */
       void delete(Class<T> clazz, K id);
       /**
        * 通过id查询
        *
        * @param clazz entity类型
        * @param id    唯一标识
        * @return entity 对象
        */
       T findById(Class<T> clazz, K id);
       /**
        * 计数
        *
        * @param clazz entity 类型
        * @return 数据条数
        */
       int count(Class<T> clazz);
       /**
        * 通过sql计数
        *
        * @param sql sql语句
        * @return 数据条数
        */
       int countBySQL(String sql);
       /**
        * 通过sql和参数计数
        *
        * @param sql      sql语句
        * @param paramMap 参数
        * @return 数据条数
        */
       int countBySQL(String sql, Map<String, Object> paramMap);
       /**
        * 查询所有数据
        *
        * @param clazz        entity类型
        * @param orderColumns 排序字段
        * @return 数据集
        */
       List<T> findALL(Class<T> clazz, OrderColumn... orderColumns);
       /**
        * hibernate 方式查询带参数
        *
        * @param clazz        entity类型
        * @param paramMap     参数
        * @param orderColumns 排序字段
        * @return 数据集
        */
       List<T> findByHql(Class<T> clazz, Map<String, Object> paramMap, OrderColumn... orderColumns);
       /**
        * hibernate 方式分页查询，带参数
        *
        * @param clazz        entity类型
        * @param paramMap     参数
        * @param pageIndex    当前页
        * @param pageSize     页面数据量
        * @param orderColumns 排序字段
        * @return 分页数据
        */
       Pagination findPageByHql(Class<T> clazz, Map<String, Object> paramMap, Object pageIndex, Object pageSize, OrderColumn... orderColumns);
       /**
        * hibernate 方式分页查询，带参数
        *
        * @param clazz        entity类型
        * @param pageIndex    当前页
        * @param pageSize     页面数据量
        * @param orderColumns 排序字段
        * @return 分页数据
        */
       Pagination findPageByHql(Class<T> clazz, Object pageIndex, Object pageSize, OrderColumn... orderColumns);
       /**
        * 通过sql和参数查询entity数据集
        *
        * @param sql      sql语句
        * @param clazz    entity类型
        * @param paramMap 参数
        * @return 数据集
        */
       List<T> findEntityBySQL(String sql, Class<T> clazz, Map<String, Object> paramMap);
       /**
        * 通过sql查询entity数据集
        *
        * @param sql   sql语句
        * @param clazz entity类型
        * @return 数据集
        */
       List<T> findEntityBySQL(String sql, Class<T> clazz);
       /**
        * 通过sql和参数查询分页数据集
        *
        * @param sql       sql语句
        * @param clazz     entity类型
        * @param paramMap  参数
        * @param pageIndex 当前页
        * @param pageSize  页面数据量
        * @return 数据集
        */
       List<T> findEntityBySQL(String sql, Class<T> clazz, Map<String, Object> paramMap, Object pageIndex, Object pageSize);
       /**
        * 通过sql查询分页数据
        *
        * @param sql       sql语句
        * @param clazz     entity类型
        * @param pageIndex 当前页
        * @param pageSize  页面数据量
        * @return 数据集
        */
       List<T> findEntityBySQL(String sql, Class<T> clazz, Object pageIndex, Object pageSize);
       /**
        * 通过sql查询二维数组
        *
        * @param sql sql语句
        * @return 数据集
        */
       List<Object[]> findListArrayBySql(String sql);
       /**
        * 通过sql和参数查询二维数组
        *
        * @param sql      sql语句
        * @param paramMap 参数
        * @return 数据集
        */
       List<Object[]> findListArrayBySql(String sql, Map<String, Object> paramMap);
       /**
        * 通过sql和参数查询分页二维数组
        *
        * @param sql       sql语句
        * @param paramMap  参数
        * @param pageIndex 当前页
        * @param pageSize  数据量
        * @return 数据集
        */
       List<Object[]> findListArrayBySql(String sql, Map<String, Object> paramMap, Object pageIndex, Object pageSize);
       /**
        * 通过sql查询List<Map> 数据
        *
        * @param sql sql语句
        * @return 数据集
        */
       List<Map<String, Object>> findListMapBySql(String sql);
       /**
        * 通过sql查询List<Map> 数据
        *
        * @param sql      sql语句
        * @param paramMap 参数
        * @return 数据集
        */
       List<Map<String, Object>> findListMapBySql(String sql, Map<String, Object> paramMap);
       /**
        * 通过sql查询分页List<Map> 数据
        *
        * @param sql       sql语句
        * @param paramMap  参数
        * @param pageIndex 当前页
        * @param pageSize  数据量
        * @return 数据集
        */
       List<Map<String, Object>> findListMapBySql(String sql, Map<String, Object> paramMap, Object pageIndex, Object pageSize);
       /**
        * 通过sql和参数查询实体分页数据
        *
        * @param sql       sql语句
        * @param clazz     entity类型
        * @param paramMap  参数
        * @param pageIndex 当前页
        * @param pageSize  数据量
        * @return 分页数据
        */
       Pagination queryPageForListEntity(String sql, Class<T> clazz, Map<String, Object> paramMap, Object pageIndex, Object pageSize);
       /**
        * 通过sql和参数查询List<Map>分页数据
        *
        * @param sql       sql语句
        * @param paramMap  参数
        * @param pageIndex 当前页
        * @param pageSize  数据量
        * @return 分页数据
        */
       Pagination queryPageForListMap(String sql, Map<String, Object> paramMap, Object pageIndex, Object pageSize);
       /**
        * 通过sql查询实体分页数据
        *
        * @param sql       sql语句
        * @param clazz     entity类型
        * @param pageIndex 当前页
        * @param pageSize  数据量
        * @return 分页数据
        */
       Pagination queryPageForListEntity(String sql, Class<T> clazz, Object pageIndex, Object pageSize);
       /**
        * 通过sql和参数查询List<Map>分页数据
        *
        * @param sql       sql语句
        * @param pageIndex 当前页
        * @param pageSize  数据量
        * @return 分页数据
        */
       Pagination queryPageForListMap(String sql, Object pageIndex, Object pageSize);
       int executeSqlUpdate(String sql, Map<String, Object> params);
       /**
        * 事务级多query执行
        *
        * @param listQuery 事务集合
        * @return 影响行数
        */
       int executeTrans(LinkedList<TranscationQuery> listQuery);
     </blockcode></pre>
   </details>

#### 5.1.4 JDBC 调用示例及介绍

JDBC接口使用，需要先在resource/xxxx-query.xml中编写sql语句，例如exampleservice-query.xml

![query.xml文件](./imgs/example-query-xml.png)

```xml
<?xml version="1.0" encoding="UTF-8"?>
<queryspace
        xmlns="http://www.w3schools.com"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="query-template.xsd"
        ref="com.sinra.ldp.example.service.impl.ExampleServiceImpl">

    <!-- 带普通参数的查询 -->
    <query id="examplelist" author="machao" remark="列表数据">
		<!--  使用【:参数名】预留参数，在#间，表示可选，如果不传，就会忽略此参数, 建议在参数和#间添加空格	-->
        <!--  根据数据库类型自动读取对应标签中的sql语句，如果不存在，默认读取sql标签下的语句  -->
        <sql>
            <![CDATA[
				select * from ldp_mcs_user_info where # user_type=:userType #
			  ]]>
        </sql>
        <oracle>
            <![CDATA[
			     select * from ldp_mcs_user_info where # user_type=:userType #
			  ]]>
        </oracle>
        <mysql>
            <![CDATA[
				select * from ldp_mcs_user_info where # user_type=:userType #
			  ]]>
        </mysql>
        <sqlserver>
            <![CDATA[
				select * from ldp_mcs_user_info where # user_type=:userType #
			  ]]>
        </sqlserver>
    </query>
</queryspace>
```

在**com.sinra.ldp.example.service.impl.ExampleServiceImpl**中使用

![jdbc查询](imgs/example-jdbc-demo.png)

通过注解**@QueryBind**自动注入配置在xml中的sql语句

```java
/**
* jdbc查询， QueryBind所对应的sql语句在resource/query/exampleservice中
* 调用时 getUserListByJDBC(StringUtils.EMPTY); 注解会将sql语句自动注入到参数中
*
* @param sql
* @return 列表数据
*/
@QueryBind("examplelist")
@Override
public List<ExampleUserInfo> getUserListByJDBC(String sql, Map<String, Object> param) {
    return jdbcDaoService.queryForList(sql, ExampleUserInfo.class, param);
}
```

更多接口

<details>
    <summary>展开</summary>
    <pre><blockcode>
     /**
     * 分页查询，带参数，返回分页对象，data类型为list:entity
     * 进行分页查询
     *
     * @param sql        sql语句
     * @param entityType entity类型
     * @param paramMap   参数
     * @param pageIndex  当前页码
     * @param pageSize   数据量
     * @return 分页数据
     */
    public <T> Pagination queryPageForListEntity(String sql, Class<T> entityType, Map<String, Object> paramMap, int pageIndex, int pageSize);
    /**
     * 分页查询，返回分页对象，data类型为list:entity
     * 进行分页查询
     *
     * @param sql        sql语句
     * @param entityType entity类型
     * @param pageIndex  当前页码
     * @param pageSize   数据量
     * @return 分页数据
     */
    public <T> Pagination queryPageForListEntity(String sql, Class<T> entityType, int pageIndex, int pageSize);
    /**
     * 分页查询，带参数，返回分页对象，data类型为list:map
     *
     * @param sql       sql语句
     * @param paramMap  参数
     * @param pageIndex 当前页
     * @param size      数据量
     * @return 分页数据
     */
    public <T> Pagination queryPageForListMap(String sql, Map<String, Object> paramMap, int pageIndex, int size);
    /**
     * 分页查询，带参数，返回分页对象，data类型为list:map
     *
     * @param sql       sql语句
     * @param pageIndex 当前页
     * @param size      数据量
     * @return 分页数据
     */
    public <T> Pagination queryPageForListMap(String sql, int pageIndex, int size);
    /**
     * 查询不带参数，返回list:map
     *
     * @param sql sql语句
     * @return 数据集
     */
    public List<Map<String, Object>> queryForList(String sql);
    /**
     * 查询带参数，返回list:map
     *
     * @param sql      sql语句
     * @param paramMap 参数
     * @return 数据集
     */
    public List<Map<String, Object>> queryForList(String sql, Map<String, Object> paramMap);
    /**
     * 查询不带参数，返回list:entity
     *
     * @param sql        sql
     * @param entityType entity类型
     * @return 数据集
     */
    public <T> List<T> queryForList(String sql, Class<T> entityType);
    /**
     * 查询带参数，返回list:entity
     *
     * @param sql        sql
     * @param entityType bean类型
     * @param paramMap   参数map
     * @return 数据集
     */
    public <T> List<T> queryForList(String sql, Class<T> entityType, Map<String, Object> paramMap);
    /**
     * 执行sql
     *
     * @param sql sql语句
     * @return 影响行数
     */
    public int excute(String sql);
    /**
     * 执行sql 带参数
     *
     * @param sql      sql语句
     * @param paramMap 参数
     * @return 影响行数
     */
    public int excute(String sql, Map<String, Object> paramMap);
    /**
     * 批量sql执行，不带参数
     *
     * @param sql sql语句
     * @return 影响行数数组
     */
    public int[] batchExcute(String... sql);
    /**
     * 执行同一sql，批量参数
     *
     * @param sql          sql语句
     * @param listParamMap 参数
     * @return 影响行数数组
     */
    public int[] execute(String sql, List<Map<String, Object>> listParamMap);
    /**
     * 事务级多query执行
     *
     * @param listQuery 事务集合
     * @return 影响行数
     */
    int executeTrans(LinkedList<TranscationQuery> listQuery);
    </blockcode></pre>
</details>


#### 5.1.5 Hibernate查询扩展——复杂条件查询

Hibernate列表查询以及分页查询在普通的条件查询上做了扩展，可以Condition来做复杂的条件查询，需要使用 **HqlWhereHelper** 工具类来构建，条件的组合有以下几种：

1. **精确查询**（EQUAL、NOTEQUAL）

   如果没有任何类型，则默认为EQUAL，也就是 “**等于**”，查询用户名称为张三的数据。

   ```java
   LinkedList<Condition> conditions = HqlWhereHelper.getInstance.and("userName","张三").buildConditions();
   //调用分页查询，传入Condition参数
   genericDaoService.findPageByConditions(ExampleUserInfo.class, conditions, page.getPageIndex(), page.getPageSize());
   ```

   如果需要其它类型，如NOTEQUAL( **不等于** )，需要显示的传递匹配类型，查询用户名称不等于张三的数据。

   ```java
   HqlWhereHelper.getInstance.and("userName", "张三", FilterType.NOTEQUAL).buildConditions();
   ```

2. **模糊查询** （LIKE）

   模糊查询所有用户名称带有张字的数据。

   ```java
   HqlWhereHelper.getInstance.and("userName", "张", FilterType.LIKE).buildConditions();
   ```

3. **条件IN**

   查询用户名称是张三或李四的数据。

   ```java
   String[] names = new String[]{"张三", "李四"};
   HqlWhereHelper.getInstance.and("userName", names, FilterType.IN).buildConditions();
   ```

4. **条件NOTIN**

   查询姓名不是张三和李四的数据。

   ```java
   String[] names = new String[]{"张三", "李四"};
   HqlWhereHelper.getInstance.and("userName", names, FilterType.NOTIN).buildConditions();
   ```

5. **组合条件**

   查询用户类型为应用类型、用户名称带有张字或者带有李字的数据。

   ```java
   HqlWhereHelper.getInstance.and("userType", "0").and("userName", "张", FilterType.LIKE).or("name", "李",FilterType.LIKE);
   ```

#### 5.1.6 SQL查询扩展——条件IN和条件LIKE

1. **条件IN**

在**exampleservice-query.xml**添加以下SQL（这里为了方便，省略了其它数据库类型标签）：

```xml
 <!--   使用in查询用户信息   -->
<query id="example_in_params" author="machao" remark="使用条件in">
    <sql>
        <![CDATA[
			select * from ldp_mcs_user_info where # id in (:ids) #
		]]>
    </sql>
</query>
```

在**ExampleServiceImpl**中的调用代码，条件IN支持**数组**与**List集合**，下面是使用数组的例子：

```java
 /**
* 使用条件in查询用户列表
*
* @param sql sql语句
* @return 列表数据
*/
@QueryBind("example_in_params")
@Override
public List<ExampleUserInfo> getListByConditionIn(String sql) {
    String[] ids = new String[]{"05bdc6e23ce64c6", "2aaa919abbba496", "2bf023cf04eb45d"};
    Map<String, Object> param = new HashMap<>();
    param.put("ids", ids);
    return jdbcDaoService.queryForList(sql, ExampleUserInfo.class, param);
}
```

2. **条件LIKE**

   在**exampleservice-query.xml**添加以下SQL，LIKE条件参数需要放到英文单引号中：

   ```xml
   <!--    使用like查询用户信息    -->
   <query id="example_like_params" author="machao" remark="使用条件like">
       <sql>select * from ldp_mcs_user_info where # user_name like '%:name%' #</sql>
   </query>
   ```

   在**ExampleServiceImpl**中的调用代码：

   ```java
   /**
   * 使用条件like查询用户列表
   *
   * @param sql sql语句
   * @return 列表数据
   */
   @QueryBind("example_like_params")
   @Override
   public List<ExampleUserInfo> getListByConditionLike(String sql) {
       Map<String, Object> param = new HashMap<>();
       param.put("name", "admin");
       return jdbcDaoService.queryForList(sql, ExampleUserInfo.class, param);
   }
   ```

### 5.2. 框架内REST服务调用示例及介绍

 对于已存在的其它服务接口，需要通过Rest 请求来调用，这里推荐使用**RestTemplate**，根据 http://api.dev.shxrtech.com/ 的API文档，使用**Nacos**自动解析服务名。

#### 5.2.1 mcs服务

根据命名规则，mcs服务名叫**mcs-sevice**，调用mcs模块的[**/user/get/page**](http://api.dev.shxrtech.com/project/25/interface/api/414)接口，拼接完整的请求地址为http://mcs-service/user/get/page ，根据文档，请求方式为GET，可选参数为pageIndex，pageSize，代码如下： 

```java
@Autowired
RestTemplate restTemplate;

/**
* rest查询，使用服务名+接口名： 这里使用mcs-service服务，调用用户获取列表接口
*
* @return 分页数据
*/
@Override
public Pagination getUserListByRest() {
    //构造请求头
    HttpHeaders headers = new HttpHeaders();
    headers.add("Accept", MediaType.APPLICATION_JSON.toString());
    HttpEntity<String> entity = new HttpEntity(headers);
    //构造请求连接
    UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl("http://mcs-service/user/get/page")
        .queryParam("pageIndex", 1)
        .queryParam("pageSize", 5);
    //发送请求
    ResponseEntity<RestResult<Pagination<Map<String, Object>>>> response = restTemplate.exchange(builder.toUriString(),
                                                                                                 HttpMethod.GET,
                                                                                                 entity,
                                                                                                 new ParameterizedTypeReference<RestResult<Pagination<Map<String, Object>>>>() {
                                                                                                 });
    //获取数据
    return response.getBody().getData();
}
```

其它接口，请参照API文档：

 http://api.dev.shxrtech.com/project/25/interface/api 


### 5.3 框架中记录多个系统之间的接口调用日志
#### 5.3.1 接口调用日志的实现
对于在系统中进行接口调用的方法中，需手动进行接口调用日志的记录。依赖**common-log**模块

```xml
<dependency>
      <groupId>com.sinra.ldp</groupId>
      <artifactId>common-log</artifactId>
      <version>1.0-SNAPSHOT</version>
</dependency>
        
```
```java
@Autowired
private InterfaceLogBaseService interfaceLogBaseService;
```
```java
InterfaceAddLogVo interfaceAddLogVo = new InterfaceAddLogVo();
        String url = "http://localhost:8800/interface/test";
        String params = "&name=lisw&number=001";
        String result= sendPost(url,params);
        interfaceAddLogVo.setIsOut(true)
                .setName("Example工程测试接口")
                .setParams(params)
                .setResult(result)
                .setUrl(url)
                .setReceiver("信睿LDP框架接收")
                .setSender("信睿LDP框架发送");
        interfaceLogBaseService.saveLog(interfaceAddLogVo,request);

```
**注：InterfaceAddLogVo字段说明：**

|  字段名称   | 说明  |
|  ----  | ----  |
| name  | 接口名称 |
| params  | 接口入参 |
| result  | 接口出参 |
| isOut  | 接口进出标识，false：进。true：出 |
| url  | 接口地址，isOut为false时，可不传，系统会通过request自动获取本系统的接口地址 |
| receiver  | 接收方 |
| sender  | 发送方 |

**注：saveLog参数说明：**

|  参数名称   | 说明  |
|  ----  | ----  |
| interfaceAddLogVo  | 日志记录内容体 |
| HttpServletRequest  | 请求，isOut为false时，可传NULL |

### 5.4 框架中使用编码规则进行自动生成编码
####  5.4.1 编码规则的创建
很多系统的业务场景当中，存在着需要自动按照顺序号、日期、关键字的组合进行生成编码。
![](imgs/example-codingrule-create.png)

#### 5.4.2 编码规则的使用
创建了编码规则之后。在应用代码中根据编码规则Key生成并获取最新的编码即可。
pom依赖**common-codingrule**模块

```xml
	<dependency>
            <groupId>com.sinra.ldp</groupId>
            <artifactId>common-codingrule</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
```
```java
    @Autowired
    private CodingruleUtils codingruleUtils;

    /**
     * 根据预先设置好的编码规则，进行生成编码。
     * codingruleUtils.getNumber(key)，业务模块代码中，可直接进行注入使用，依赖common-codingrule包
     * @param key 编码规则的Key
     * @return
     *
     */
    @GetMapping("/generateNumber")
    public RestResult generateNumber(String key){

        String number = codingruleUtils.getNumber(key);
        /**
         * 获取到number后，set到业务实体中即可
         * 业务代码.......
         *
         */
        return new RestResult(ResultMsg.SUCCESS,number);
    }
    
```
### 5.5 框架中使用数据字典
#### 5.5.1 数据字典的关联使用
各业务模块实体中，如果有需要用到数据字典的字段，则将数据字典的主键ID存入到业务实体当中即可。在业务实体当中利用Hibernate关联即可。

```java
	/**
     * 人员性别
     */
    @ManyToOne(fetch = FetchType.EAGER,cascade = {CascadeType.REFRESH})
    @JoinColumn(name = "sex")
    private DictInfo2 sex;//数据类别
   
```

### 5.6 框架中记录JOB的执行日志

#### 5.6.1 定时任务的创建
在MCS管理后台中，系统维护-定时任务功能模块中进行创建。

![](imgs/example-job-create-form.png)

**注：字段说明：**

|  字段名称   | 说明  |
|  ----  | ----  |
| 任务名称  | 定时任务的名称 |
| 接口地址  | 定时任务开启之后，执行调用的接口地址,接口地址路径中必须包含'ldpjob/'字符串，例如：xx/ldpjob/xx |
| cron表达式  | 配置cron表达式 |
| 按固定时间执行  |  |
| 请求方式  | 接口地址的请求方式GET还是POST |
| POST参数  | POST格式的参数 |
| 备注  |  |

创建任务之后，在列表中点击第一个按钮手动开启任务，任务开始后，到达设置的时间后则执行定时任务（调用定时任务配置的接口）。
![](imgs/example-job-start.png)

#### 5.6.2 在定时任务执行的接口上增加JOB日志记录
由于系统架构为微服务，服务与服务之间解耦合。因此定时任务设计之初，考虑为定时接口任务调用。  
定时任务执行接口调用， 在被调用接口引用的Service具体业务方法添加此@SysJobLog 注解。 
注解包含两大功能，1 异步执行，防止接口执行时间过长，否则定时任务调用机器一直连接被占用； 2 更新定时任务执行情况。 
定时任务执行流程：

1. 定时任务调度服务启动后，满足定时条件自动执行任务调度。 根据后台配置好的服务名称、接口信息调用其他服务接口。
2. 被调用接口，必须添加@RequestParam String taskJobId 作为接收参数，此参数后续作为更新任务执行状态使用。由于在业务方法上添加@SysJobLog， 业务方法与Controller不在同一个线程中，无法
   获取上一个线程中的信息。因此具体业务Service也必须添加名称为String taskJobId 的参数，后续会自动获取参数的值进行更新任务执行情况。
3. 被调用接口也必须添加 @AvoidRepeatableSubmit 防止重复执行   

Controller 

```java

    @PostMapping(value = "/testExampleJob")
    @AvoidRepeatableSubmit
    public RestResult testJob(@RequestParam String taskJobId, @RequestBody ExampleUserInfo userInfo){
	        exampleService.doJob (taskJobId,userInfo);
        return new RestResult(ResultMsg.SUCCESS,"");
    }
    
```

具体Service

```java

    @Override
    @AutoService
    @SysJobLog
    public void doJob(String taskJobId,ExampleUserInfo exampleUserInfo) {
        genericDaoService.insert(exampleUserInfo);
    }
    
```

### 5.7 文件上传与下载

#### 5.7.1 IRepoFileService介绍

IRepoFileService是LDP框架提供的一个文件上传下载的服务，文件存放路径在基础服务配置文件中

```yml
ldp:
  query:
    location: query/*.xml
    dialect: mysql
   # 上传文件存放地址
  file:
    home-dir: .ldp
    file-dir: repo
```

应用配置文件中需要编写上传文件大小限制的配置bootstrap.yml：

```yml
spring:
  servlet:
    multipart:
      # 设置单个文件大小
      max-file-size: 1024MB
      # 总上传数据的大小
      max-request-size: 2048MB
```

#### 5.7.2 文件上传

使用 **org.apache.dubbo.config.annotation** 包中的 **@Reference** 注解，调用upload方法后，获取到一个 **ProcessResult** 对象，判断upload是否正常执行result.isExecute()，以下是代码案例：

```java
/**
* 文件上传下载服务
*/
@Reference
IRepoFileService repoFileService;

/**
* 上传文件
*
* @param file
* @return 文件ID
*/
@Override
public String upload(MultipartFile file) {
    try {
        //上传后获取到ProcessResult
        ProcessResult<String> result = repoFileService.upload(file.getOriginalFilename(), file.getBytes());
        //判断是否上传成功
        if (result.isExecute()) {
            return result.getResultData();
        } else {
            logger.error(result.getStackTraceString());
            throw new RuntimeException(result.getMessage());
        }
    } catch (IOException e) {
        logger.error(e.getMessage(), e);
        e.printStackTrace();
    }
    return "";
}
```

#### 5.7.3 文件下载

下载接口有2个方法，一个是只下载文件数据，另外一个是包含文件的其它信息（文件名、格式等），同样是获取到 **ProcessResult** 对象，后续判断后进行拆包，拿到下载后的数据。

```java
/**
* 下载文件
*
* @param fileId 文件ID
* @return 文件数据
*/
@Override
public LdpSysFileResource download(String fileId) {
    // 仅包含文件数据
    //ProcessResult<byte[]> result = repoFileService.download(fileId);

    //包含文件名、文件类型、文件数据
    ProcessResult<LdpSysFileResource> result = repoFileService.downloadFileResource(fileId);
    if (result.isExecute()) {
        return result.getResultData();
    } else {
        logger.error(result.getStackTraceString());
        throw new RuntimeException(result.getMessage());
    }
}
```

#### 5.7.4 文件删除

```java
/**
* 删除文件
*/
@Override
public boolean delFile(String fileId) {
    ProcessResult result = repoFileService.del(fileId);
    return result.isExecute();
}
```



## 六、子模块创建

### 6.1. 子工程创建步骤

#### 6.1.1 创建module

右键工程->new->Module

![](imgs/example-new-module.png)

在弹窗中选择JDK版本,然后点击NEXT

![选择jdk](imgs/example-new-dialog.png)

选择父节点、按照命名规范输入对应的**模块名称**

![模块名](imgs/example-new-module-name.png)

点击**FINISH**

![demo模块](imgs/example-module-demo.png)

#### 6.1.2 配置POM文件

```xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>app-example</artifactId>
        <groupId>com.sinra.ldp</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>example-demo</artifactId>
    <!--  配置版本号和打包方式 -->
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <dependencies>
        <!-- 业务公共依赖库，包含自动填充注解、基础Dao、Rest参数注解等通用工具 -->
        <dependency>
            <groupId>com.sinra.ldp</groupId>
            <artifactId>mcs-common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!-- rest依赖库，主要包含返回结果包装以及异常处理 -->
        <dependency>
            <groupId>com.sinra.ldp</groupId>
            <artifactId>common-rest</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!-- query依赖库，主要包含sql语句xml解析，绑定等 -->
        <dependency>
            <groupId>com.sinra.ldp</groupId>
            <artifactId>common-query</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!-- cache依赖库，主要包含缓存注解，及缓存处理逻辑 -->
        <dependency>
            <groupId>com.sinra.ldp</groupId>
            <artifactId>common-cache</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.sinra.ldp</groupId>
            <artifactId>example-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>
```

#### 6.1.3 编写yml、logback等文件（startup模块）

**注：非启动模块不需要这一步骤**

在resources目录下新建bootstrap.yml、bootstrap-nacos.yml、bootstrap-local.yml、bootstrap-dev.yml（**内部调试用，可以忽略**）配置文件。

**bootstrap.yml**: 这个文件主要配置端口、nacos地址、服务名、程序启动时启用配置

```yml
server:
  # 服务端口
  port: 8800
nacos:
  # nacos地址
  server: nacos-server0:8018

spring:
  profiles:
    # 默认启用nacos配置
    active: nacos
  application:
    #服务名
    name: example-service
  messages:
    encoding: UTF-8
  cloud:
    nacos:
      server-addr: ${nacos.server}
      config:
        server-addr: ${nacos.server}
      discovery:
        server-addr: ${nacos.server}


```

**bootstrap-nacos.yml**：这个是基础服务在远程时启用的配置，文中内容包含配置nacos配置、缓存、Dubbo等等相关配置。

```yml
spring:
  cloud:
    # nacos 配置
    nacos:
      discovery:
        register-enabled: true
        group: nacos_default_group
        # 服务消费者和提供者在不同网段，且本服务作为提供者时，配置为提供者实际部署机器名地址，并在消费者hosts文件中配置机器名与ip的映射
#        ip: ldp-server0
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8

ldp:
  query:
    location: query/*.xml
    dialect: mysql
  cache:
    redis:
      host: redis-server0
      port: 7268
      password: cacheForldp2020
      database: 1
      sentinel:
        master: ldp-master
        nodes:
          - redis-server0:6380
          - redis-server0:6381
          - redis-server0:6382
    instances:
      #用户会话级缓存
      session: 3600
      #应用级缓存默认不过期
      application: -1
  nacos:
    group: DEFAULT_GROUP
    requestTimeout: 5000


demo:
  service:
    version: 1.0


dubbo:
  registry:
    address: nacos://${spring.cloud.nacos.server-addr}
  application:
    ##日志适配
    logger: slf4j
    ##输出访问日志
  protocol:
    accesslog: true
    serialization: java

  consumer:
    application: ${spring.application.name}-consumer
    parameters:
      protocol: dubbo
      serialization: java
      version: 1.0
      timeout: 15000
      group:  ${spring.cloud.nacos.discovery.group}
```

**bootstrap-local.yml**：当基础服务ldp-base运行在本地时，可以使用此配置

```yml
## 本地配置
spring:
  cloud:
    nacos:
      discovery:
        group: local-default-group
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8

ldp:
  query:
    location: query/*.xml
    dialect: mysql
  cache:
    redis:
      host: sinra-server0
      port: 7268
      password: cacheForldp2020
      database: 1
      sentinel:
        master: ldp-master
        nodes:
          - sinra-server0:6380
          - sinra-server0:6381
          - sinra-server0:6382
    instances:
      # 用户会话级缓存
      session: 3600
      # 应用级缓存默认不过期
      application: -1
  nacos:
    group: DEFAULT_GROUP
    requestTimeout: 5000


demo:
  service:
    version: 1.0


dubbo:
  registry:
    address: nacos://${spring.cloud.nacos.server-addr}
  application:
    ## 日志适配
    logger: slf4j
    ## 输出访问日志
  protocol:
    accesslog: true
    serialization: java
  consumer:
    parameters:
      protocol: dubbo
      version: 1.0
      timeout: 15000
      group:  ${spring.cloud.nacos.discovery.group}
```

**logback.xml**：主要做为日志文件配置，修改**ldp-app-example**为自己对应工程名，其余配置根据自己需求修改

```xml
<?xml version="1.0" encoding="UTF-8"?>
    <configuration>

        <property name="LOG_FILE" value="ldp-app-example" />

        <include resource="org/springframework/boot/logging/logback/defaults.xml" />
        <include resource="org/springframework/boot/logging/logback/console-appender.xml" />
        <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <File>${user.home}/.sinra/${LOG_FILE}.log</File>
            <encoder>
                <pattern>%date [%level] [%thread] %logger{60} [%file : %line] %msg%n</pattern>
            </encoder>
            <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">

                <!-- 按文件名2018-08-29-15[0].log，如果文件大小超过100kb,则继续分割日志2018-08-29-15-32[1].log,
       2018-08-29-15-32[2].log。%d和%i不能缺少 -->
                <fileNamePattern>${user.home}/.sinra/${LOG_FILE}.%d{yyyy-MM-dd.HH}_%i.log</fileNamePattern>
                <maxFileSize>10MB</maxFileSize>
                <maxHistory>30</maxHistory>
                <totalSizeCap>1GB</totalSizeCap>
                <!-- 保留1G日志 -->
            </rollingPolicy>
        </appender>

        <root level="INFO">
            <appender-ref ref="CONSOLE"/>
            <appender-ref ref="FILE"/>
        </root>
    </configuration>
```

#### 6.1.4 编写启动类（startup模块）

**注：非启动模块不需要这一步骤**

根据第三章命名规则，在**src/main/java**中新建包名**com.sinra.ldp.[projectname]**，这里 演示为**com.sinra.ldp.example**。在包下面新建**LdpExampleApplication.java**（请根据自己项目名称自行修改）,内容如下：

```java
@SpringBootApplication
@EnableCaching
@EnableDiscoveryClient
@ComponentScan("com.sinra.ldp")
public class LdpExampleApplication {
    public static void main(String[] args) {
        new SpringApplicationBuilder(LdpExampleApplication.class).build()
                .run(args);
    }
}
```

#### 6.1.5 实体类创建

打开IDEA的Settings->Plugins，在市场中搜索Lombok并安装Lombox插件。

![安装lombok](./imgs/example-idea-lombok.png)

在[projectname]-api模块中，根据对应的表编写实体类。下面是部分代码（完整代码请看**com.sinra.ldp.model.example.ExampleUserInfo.java**）：

```java
@Table(name = "ldp_mcs_user_info")
@Getter
@Setter
@Entity
@NoArgsConstructor
public class ExampleUserInfo implements Serializable {
    
    private static final long serialVersionUID = -4556501954921265597L;

    /**
     * 主键
     */
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO, generator = "custom-uuid")
    @GenericGenerator(name = "custom-uuid", strategy = CustomUUIDGenerator.STRATEGY_UUID)
    private String id;

    /**
     * 密码
     */
    @Column(name = "user_password")
    @AutoComputed(command = ComputedCommand.MD5)
    private String userPassword;

    /**
     * 用户名称
     */
    @Column(name = "user_name")
    private String userName;

    /**
     * 创建人ID
     */
    @Column(name = "create_id")
    @AutoComputed
    private String createId;

    /**
     * 创建时间
     */
    @Column(name = "create_time")
    @AutoComputed(command = ComputedCommand.DATETIME)
    private Date createTime;

    /**
     * 更新人ID
     */
    @Column(name = "update_id")
    @AutoComputed(command = ComputedCommand.USERID)
    private String updateId;

    /**
     * 排序字段
     */
    @JsonIgnore
    @Column(name = "sort_name")
    @AutoComputed(command = ComputedCommand.PINYIN, ref = "userName")
    private String sortName;

    public ExampleUserInfo(String id) {
        this.id = id;
    }
}
```

类注解：**@Table**：数据表、**@Getter**：自动生成get方法、**@Setter**：自动生成set方法、**@Entity**：指定此类是一个实体**@NoArgsConstructor**：生成无参构造方法。

字段注解：**@Id** 唯一标识、**@GeneratedValue**标识id为自动生成、**@GenericGenerator**指定生成器、**@Column**指定关联的表字段

**@AutoComputed**标识字段根据规则进行自动填充，需要在Service层搭配**@AutoService**注解使用。例如

```java
@Column(name = "create_time")
@AutoComputed(command = ComputedCommand.DATETIME)
private Date createTime;
```

这里会以当前时间填充到**createTime**字段中。

目前command支持6种自动填充，分别为：

**AUTO**：根据字段名进行填充。

**MD5**：将字段值进行一次md5加密。

**DATETIME**：当前时间。

**USERID**：当前登录用户。

**UUID**：生成32位随机字符串。

**PINYIN**：将ref字段的拼音填充到此字段中

#### 6.1.6 业务代码开发

写好实体类后，就可以进行业务代码开发了。参照example-biz模块目录结构，主要分为视图层rest、service层、其它config、util根据自己需要创建，工程目录结构如下：

![example-biz](./imgs/example-biz-struct.png)

1. 在src/main/java中新建包名**com.sinra.ldp.example.demo**，并新建**rest**、**service**包。

2. 创建**DemoService**接口，后续有新增接口，都先写到这里。

![demoService接口](./imgs/example-new-interface.png)

3. 在**service**包下面新建实现类包**impl**，并创建实现类**DemoServiceImpl**，实现**DemoService**接口，添加**@Service**注解，实现所有的方法，并填上相应的实现代码。

![实现类](./imgs/example-new-impl.png)

4. 在rest包下面创建**DemoRest.java**，添加**@RestController**、**@RequestMapping("demo")**注解，注入DemoService，并开发相应的请求接口

   ![Rest代码](./imgs/example-new-rest.png)

   这样一个列表接口就开发好了。

#### 6.1.7 SQL配置xml文件

如果有自定义sql的需求，在resources下新建query目录，并从example-biz模块resources目录下将**query-template.xsd**、**exampleservice-query.xml**拷贝到新建的**query**目录下。将exampleserrvice-query.xml修改为**demoservice-query.xml**，并将ref修改为**DemoServiceImpl**

![sql文件](./imgs/example-demo-sql.png)

参照query标签编写自己的sql语句，在ServiceImpl中使用**@QueryBind**来注入sql语句。

```java
/**
* jdbc查询， QueryBind所对应的sql语句在resource/query/exampleservice中
* 调用时 getUserListByJDBC(StringUtils.EMPTY); 注解会将sql语句自动注入到参数中
*
* @param sql
* @return 列表数据
*/
@QueryBind("examplelist")
@Override
public List<ExampleUserInfo> getUserListByJDBC(String sql, Map<String, Object> param) {
    return jdbcDaoService.queryForList(sql, ExampleUserInfo.class, param);
}
```

方法调用：

```java
exampleServcie.getUserListByJDBC(StringUtils.EMPTY);
```

### 6.2. 运行调试

先在Terminal执行以下shell命令：

```shell
mvn clean install -U
#编译成功后
cd example-api
#上传实体类
mvn ldp:upload
```

开发时运行，可以直接点击IDEA的run按钮或者debug按钮

![运行调试按钮](./imgs/example-run.png)

也可以在Terminal中执行以下命令

```shell
# 如果当前为exampl-api
# cd ..
cd example-startup
java -jar example-startup-1.0-SNAPSHOT.jar
```

正确运行后，根据端口和接口地址拼接请求地址和参数，建议使用**POSTMAN**，get方法可以使用浏览器访问，例如访问http://localhost:8800/example/get/list

![请求案例](./imgs/example-request.png)



## 七、打包部署

开发完成后，可以将打包后的jar包，上传到服务器，使用以下命令后台启动。这里的方法仅供参考，也可以选择用其它方式。

```shell
nohup java -jar example-startup-1.0-SNAPSHOT.jar &
```

## 八、样例工程源码下载

### 8.1. git下载

```shell
git clone http://gitlab.dev.shxrtech.com/ldp/ldp-app-example.git
```

### 8.2. http下载

http://gitlab.dev.shxrtech.com/ldp/ldp-app-example/-/archive/master/ldp-app-example-master.zip

注：下载源码需注册信睿开发账号，账号申请发送邮件至: machao@shxrtech.com, qianli.ma@shxrtech.com 





·