第四十四章: 基於SpringBoot & AOP完成統一資源自動查詢映射

本章內容比較偏向系統設計方面,簡單的封裝就能夠應用到系統中使用,從而提升咱們的編碼效率以及代碼的可讀性。統一資源在系統內是不可避免的模塊,資源分類也有不少種,比較常見如:圖片資源、文本資源、視頻資源等,那麼資源統一處理的好處是什麼呢?你們有可能會有疑問,我把資源存放到業務表內豈不更好嗎?這樣查詢起來也方便,並不須要關聯資源信息表!固然設計不分好壞,只有更適合、更簡單!接下來帶着疑問進入本章的內容。java

本章目標

基於SpringBoot平臺結合AOP完成統一資源的自動查詢映射。mysql

構建項目

本章使用到的依賴相對來講比較多,大體:WebMapStructSpringDataJpaLomBok等,數據庫方面採用MySQL來做爲數據支持。android

數據初始化

本章用到的數據表結構以及初始化的數據以前都是放在項目的resources目錄下,爲了你們使用方面我在這裏直接貼出來,以下所示:git

--
-- Table structure for table `hy_common_resource`
--

DROP TABLE IF EXISTS `hy_common_resource`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `hy_common_resource` (
  `CR_ID` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵自增',
  `CR_TARGET_ID` varchar(36) DEFAULT 'NULL' COMMENT '所屬目標編號,關聯其餘信息表主鍵,如:用戶頭像關聯用戶編號',
  `CR_TYPE_ID` varchar(36) DEFAULT NULL COMMENT '資源類型編號',
  `CR_URL` varchar(200) DEFAULT 'NULL' COMMENT '資源路徑,如:圖片地址',
  `CR_CREATE_TIME` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp() COMMENT '資源添加時間',
  `CR_ORDER` int(11) DEFAULT 0 COMMENT '排序字段',
  PRIMARY KEY (`CR_ID`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COMMENT='系統資源信息表';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Dumping data for table `hy_common_resource`
--

LOCK TABLES `hy_common_resource` WRITE;
/*!40000 ALTER TABLE `hy_common_resource` DISABLE KEYS */;
INSERT INTO `hy_common_resource` VALUES (1,'bc4c8e38-edd6-11e7-969c-3c15c2e4a8a6','ce66916c-edd7-11e7-969c-3c15c2e4a8a6','https://upload.jianshu.io/users/upload_avatars/4461954/f09ba256-f6db-41ed-a4ac-b2d23737f0ac.jpg?imageMogr2/auto-orient/strip|imageView2/1/w/96/h/96','2017-12-31 03:08:46',0),(2,'bc4c8e38-edd6-11e7-969c-3c15c2e4a8a6','f84f12c4-edd7-11e7-969c-3c15c2e4a8a6','https://upload.jianshu.io/collections/images/358868/android.graphics.Bitmap_d88b4de.jpeg?imageMogr2/auto-orient/strip|imageView2/1/w/240/h/240','2017-12-31 03:12:38',0),(3,'bc4c8e38-edd6-11e7-969c-3c15c2e4a8a6','f84f12c4-edd7-11e7-969c-3c15c2e4a8a6','https://upload.jianshu.io/collections/images/522928/kafka_diagram.png?imageMogr2/auto-orient/strip|imageView2/1/w/240/h/240','2017-12-31 09:13:32',0);
/*!40000 ALTER TABLE `hy_common_resource` ENABLE KEYS */;
UNLOCK TABLES;

--
-- Table structure for table `hy_common_resource_type`
--

DROP TABLE IF EXISTS `hy_common_resource_type`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `hy_common_resource_type` (
  `CRT_ID` varchar(36) NOT NULL COMMENT '類型編號',
  `CRT_NAME` varchar(20) DEFAULT NULL COMMENT '類型名稱',
  `CRT_FLAG` varchar(30) DEFAULT NULL COMMENT '資源標識',
  `CRT_CREATE_TIME` timestamp NOT NULL DEFAULT current_timestamp() COMMENT '建立時間',
  PRIMARY KEY (`CRT_ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='資源類型信息表';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Dumping data for table `hy_common_resource_type`
--

LOCK TABLES `hy_common_resource_type` WRITE;
/*!40000 ALTER TABLE `hy_common_resource_type` DISABLE KEYS */;
INSERT INTO `hy_common_resource_type` VALUES ('ce66916c-edd7-11e7-969c-3c15c2e4a8a6','用戶頭像','USER_HEAD_IMAGE','2017-12-31 03:07:59'),('f84f12c4-edd7-11e7-969c-3c15c2e4a8a6','用戶背景圖片','USER_BACK_IMAGE','2017-12-31 03:09:09');
/*!40000 ALTER TABLE `hy_common_resource_type` ENABLE KEYS */;
UNLOCK TABLES;

--
-- Table structure for table `hy_user_info`
--

DROP TABLE IF EXISTS `hy_user_info`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `hy_user_info` (
  `UI_ID` varchar(36) NOT NULL COMMENT '主鍵',
  `UI_NAME` varchar(10) DEFAULT NULL COMMENT '名稱',
  `UI_NICK_NAME` varchar(20) DEFAULT NULL COMMENT '暱稱',
  `UI_AGE` int(11) DEFAULT NULL COMMENT '年齡',
  `UI_ADDRESS` varchar(50) DEFAULT NULL COMMENT '所居地',
  PRIMARY KEY (`UI_ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用戶基本信息表';
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Dumping data for table `hy_user_info`
--

LOCK TABLES `hy_user_info` WRITE;
/*!40000 ALTER TABLE `hy_user_info` DISABLE KEYS */;
INSERT INTO `hy_user_info` VALUES ('bc4c8e38-edd6-11e7-969c-3c15c2e4a8a6','hengboy','恆宇少年',23,'山東省濟南市');
/*!40000 ALTER TABLE `hy_user_info` ENABLE KEYS */;
UNLOCK TABLES;
複製代碼

用到的數據庫爲resources,能夠自行建立或者更換其餘數據庫使用。web

搭建項目

本章咱們把統一資源單獨拿出來做爲一個項目子模塊來構建,而用戶服務做爲另一個單獨模塊構建,下面先來貼出父項目的pom.xml配置文件內容,以下所示:spring

....//
<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.5.9.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
		<org.mapstruct.version>1.2.0.Final</org.mapstruct.version>
	</properties>

	<dependencies>
		<!--mapStruct-->
		<dependency>
			<groupId>org.mapstruct</groupId>
			<artifactId>mapstruct-jdk8</artifactId>
			<version>${org.mapstruct.version}</version>
		</dependency>
		<dependency>
			<groupId>org.mapstruct</groupId>
			<artifactId>mapstruct-processor</artifactId>
			<version>${org.mapstruct.version}</version>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>javax.inject</groupId>
			<artifactId>javax.inject</artifactId>
			<version>1</version>
		</dependency>
		<!--Spring data jpa-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
		<!--web-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<!--MySQL-->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>
		<!--Lombok-->
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<!--druid-->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid-spring-boot-starter</artifactId>
			<version>1.1.6</version>
		</dependency>
	</dependencies>
....//
複製代碼

接下來咱們開始建立common-resource子模塊,將資源處理徹底獨立出來,在建立子模塊時要注意package命名要保證能夠被SpringBoot運行時掃描到!!!sql

common-resource

咱們須要先建立一個BaseEntity做爲全部實體的父類存在,以下所示:數據庫

/**
 * 全部實體的父類
 * 做爲類型標識存在
 * @author yuqiyu
 * ========================
 * Created with IntelliJ IDEA.
 * User:恆宇少年
 * Date:2017/12/31
 * Time:下午3:35
 * 碼雲:http://git.oschina.net/jnyqy
 * ========================
 */
public class BaseEntity
    implements Serializable{}
複製代碼

該類僅僅實現了Serializable接口,在建立業務實體時須要繼承該類,這也是基本的設計規則,方便後期添加全局統一的字段或者配置。bash

  • 資源實體
/**
 * 資源實體
 * @author yuqiyu
 * ========================
 * Created with IntelliJ IDEA.
 * User:恆宇少年
 * Date:2017/12/31
 * Time:上午11:21
 * 碼雲:http://git.oschina.net/jnyqy
 * ========================
 */
@Data
@Entity
@Table(name = "hy_common_resource")
public class CommonResourceEntity
    extends BaseEntity
{
    /**
     * 資源編號
     */
    @Column(name = "CR_ID")
    @Id
    @GeneratedValue
    private Integer resourceId;
    /**
     * 資源所屬目標編號
     */
    @Column(name = "CR_TARGET_ID")
    private String targetId;
    /**
     * 類型編號
     */
    @Column(name = "CR_TYPE_ID")
    private String typeId;
    /**
     * 資源路徑
     */
    @Column(name = "CR_URL")
    private String resourceUrl;
    /**
     * 建立時間
     */
    @Column(name = "CR_CREATE_TIME")
    private Timestamp createTime;
    /**
     * 排序
     */
    @Column(name = "CR_ORDER")
    private int order;
}
複製代碼
  • 資源類型實體
/**
 * 資源類型實體
 * @author yuqiyu
 * ========================
 * Created with IntelliJ IDEA.
 * User:恆宇少年
 * Date:2017/12/31
 * Time:上午11:22
 * 碼雲:http://git.oschina.net/jnyqy
 * ========================
 */
@Data
@Entity
@Table(name = "hy_common_resource_type")
public class CommonResourceTypeEntity
    extends BaseEntity
{
    /**
     * 類型編號
     */
    @Id
    @Column(name = "CRT_ID")
    @GeneratedValue(generator = "system-uuid")
    @GenericGenerator(name = "system-uuid", strategy = "uuid")
    private String id;
    /**
     * 類型名稱
     */
    @Column(name = "CRT_NAME")
    private String name;
    /**
     * 類型標識
     */
    @Column(name = "CRT_FLAG")
    private String flag;
    /**
     * 類型添加時間
     */
    @Column(name = "CRT_CREATE_TIME")
    private Timestamp createTime;
}
複製代碼

下面咱們來建立對應實體的數據接口,咱們採用SpringDataJPA的方法名查詢規則來查詢對應的數據。mvc

  • 資源數據接口
/**
 * 資源數據接口
 * @author yuqiyu
 * ========================
 * Created with IntelliJ IDEA.
 * User:恆宇少年
 * Date:2017/12/31
 * Time:上午11:31
 * 碼雲:http://git.oschina.net/jnyqy
 * ========================
 */
public interface CommonResourceRepository
    extends JpaRepository<CommonResourceEntity,Integer>
{
    /**
     * 根據類型編號 & 目標編號查詢出資源實體
     * @param typeId 類型編號
     * @param targetId 目標編號
     * @return
     */
    List<CommonResourceEntity> findByTypeIdAndTargetId(String typeId, String targetId);
}
複製代碼
  • 資源類型數據接口
/**
 * 資源類型數據接口
 * @author yuqiyu
 * ========================
 * Created with IntelliJ IDEA.
 * User:恆宇少年
 * Date:2017/12/31
 * Time:上午11:32
 * 碼雲:http://git.oschina.net/jnyqy
 * ========================
 */
public interface CommonResourceTypeRepository
    extends JpaRepository<CommonResourceTypeEntity,String>
{
    /**
     * 根據類別標識查詢
     * @param flag 資源類型標識
     * @return
     */
    CommonResourceTypeEntity findTopByFlag(String flag);
}
複製代碼

接下來咱們開始編寫根據資源類型獲取指定目標編號的資源列表業務邏輯方法,建立名爲CommonResourceService統一資源業務邏輯實現類,以下所示:

/**
 * 公共資源業務邏輯實現類
 * @author yuqiyu
 * ========================
 * Created with IntelliJ IDEA.
 * User:恆宇少年
 * Date:2017/12/31
 * Time:下午4:18
 * 碼雲:http://git.oschina.net/jnyqy
 * ========================
 */
@Service
@Transactional(rollbackFor = Exception.class)
public class CommonResourceService {
    /**
     * 資源類型數據接口
     */
    @Autowired
    private CommonResourceTypeRepository resourceTypeRepository;
    /**
     * 資源數據接口
     */
    @Autowired
    private CommonResourceRepository resourceRepository;

    /**
     * 根據資源標識 & 所屬目標編號查詢資源路徑路邊
     *
     * @param resourceFlag 資源標識
     * @param targetId     目標編號
     * @return
     */
    public List<String> selectUrlsByFlag(CommonResourceFlag resourceFlag, String targetId) throws Exception {
        /**
         * 獲取資源類型
         */
        CommonResourceTypeEntity resourceType = selectResourceTypeByFlag(resourceFlag);
        /**
         * 查詢該目標編號 & 類型的資源列表
         */
        List<CommonResourceEntity> resources = resourceRepository.findByTypeIdAndTargetId(resourceType.getId(), targetId);

        return convertUrl(resources);
    }

    /**
     * 轉換路徑
     * 經過實體集合轉換成路徑集合
     *
     * @param resources 資源實體列表
     * @return
     */
    List<String> convertUrl(List<CommonResourceEntity> resources) {
        List<String> urls = null;
        if (!ObjectUtils.isEmpty(resources)) {
            urls = new ArrayList();
            for (CommonResourceEntity resource : resources) {
                urls.add(resource.getResourceUrl());
            }
        }

        return urls;
    }

    /**
     * 根據資源類型標識查詢資源類型基本信息
     *
     * @param resourceFlag 資源類型標識
     * @return
     * @throws Exception
     */
    CommonResourceTypeEntity selectResourceTypeByFlag(CommonResourceFlag resourceFlag) throws Exception {
        /**
         * 查詢資源類型
         */
        CommonResourceTypeEntity resourceType = resourceTypeRepository.findTopByFlag(resourceFlag.getName());
        if (ObjectUtils.isEmpty(resourceFlag)) {
            throw new Exception("未查詢到資源");
        }
        return resourceType;
    }

}
複製代碼

CommonResourceService提供了對外的方法selectUrlsByFlag能夠查詢指定目標編號 & 指定類型的多個資源地址。

統一資源映射

common-resource子模塊項目內添加統一資源的相關映射內容,咱們預計的目標效果是根據咱們自定義的註解結合AOP來實現指定方法的結果處理映射,咱們須要建立兩個自定義的註解來完成咱們的預想效果,註解分別爲:ResourceFieldResourceMethod,下面咱們來看看ResourceField註解的屬性定義,以下所示:

/**
 * 配置統一資源字段
 * 該註解配置在普通字段上,根據配置信息自動查詢對應的資源地址
 * Demo:
 *
 * @ResourceField(flag=CommonResourceFlagEnum.SHOP_COVER_IMG)
 * private String shopCoverImage;
 *
 * 其中multiple不須要配置,由於封面只有一張,使用默認值便可
 * flag設置爲對應的資源標識,資源類型不存在時不執行查詢
 * @ResourceTargetId 若是註解不存在或目標編號不存在或者爲null、""時不執行查詢資源
 *
 * @author:於起宇 <br/>
 * ===============================
 * Created with Eclipse.
 * Date:2017/12/31
 * Time:13:11
 * 簡書:http://www.jianshu.com/u/092df3f77bca
 * ================================
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
public @interface ResourceField {

    /**
     * 讀取資源是單條或者多條
     * true:讀取多條資源地址,對應設置到List<String>集合內
     * false:讀取單條資源地址,對應設置配置ResourceField註解的字段value
     * @return
     */
    boolean multiple() default false;

    /**
     * 配置讀取統一資源的標識類型
     * @return
     */
    CommonResourceFlag flag();

    /**
     * 若是配置該字段則不會去找@Id配置的字段
     * 該字段默認爲空,則默認使用@Id標註的字段的值做爲查詢統一資源的target_id
     * @return
     */
    String targetIdField() default "";
}
複製代碼

ResourceField註解用於配置在查詢結果的字段上,如:咱們查詢用戶頭像時定義的字段爲userHeadImage,咱們這時僅僅須要在userHeadImage字段上添加ResourceField便可。 另一個註解ResourceMethod的做用僅僅是爲了AOP根據該註解切面方法,也是隻有被該註解切面的方法纔會去執行AOP切面方法的返回值進行處理,代碼以下所示:

/**
 * 配置指定方法將會被AOP切面類ResourceAspect所攔截
 * 攔截後會根據自定義註解進行查詢資源 & 設置資源等邏輯
 * @author:於起宇 <br/>
 * ===============================
 * Created with Eclipse.
 * Date:2017/12/15
 * Time:14:04
 * 簡書:http://www.jianshu.com/u/092df3f77bca
 * ================================
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface ResourceMethod { }
複製代碼

咱們的自定義註解已經編寫完成,轉過頭來咱們先看看@Around切面方法所須要的邏輯實現方法,建立ResourcePushService接口添加以下兩個方法:

/**
 * 統一資源設置業務邏輯定義接口
 * @author:於起宇 <br/>
 * ===============================
 * Created with Eclipse.
 * Date:2017/12/15
 * Time:14:58
 * 簡書:http://www.jianshu.com/u/092df3f77bca
 * ================================
 */
public interface ResourcePushService
{
    /**
     * 設置單個實例的資源信息
     * @param object 須要設置資源的實例
     */
    void push(Object object) throws Exception;

    /**
     * 設置多個實例的資源信息
     * @param objectList 須要設置資源的實例列表
     */
    void push(List<Object> objectList) throws Exception;
}
複製代碼

分別提供了設置單個、多個資源的方法,因爲實現類內容比較多這裏就不貼出具體的實現代碼了,詳細請下載源碼進行查看,源碼地址:spring-boot-chapter內的Chapter44項目。

資源切面類

咱們一直都在說資源統一切面映射,那麼咱們的資源的切面該如何去配置切面切入點呢?在以前咱們建立了ResourceMethod註解,咱們就用它做爲方法切入點完成切面的環繞實現, ResourceAspect代碼以下所示:

/**
 * 統一資源Aop切面定義
 * 根據自定義註解配置自動設置配置的資源類型到指定的字段
 * @author:於起宇 <br/>
 * ===============================
 * Created with Eclipse.
 * Date:2017/12/15
 * Time:14:05
 * 簡書:http://www.jianshu.com/u/092df3f77bca
 * ================================
 */
@Component
@Aspect
public class ResourceAspect
{
    /**
     * logback
     */
    Logger logger = LoggerFactory.getLogger(ResourceAspect.class);

    /**
     * 資源處理業務邏輯
     */
    @Autowired
    @Qualifier("ResourcePushSupport")
    ResourcePushService resourcePushService;

    /**
     * 資源設置切面方法
     * 攔截配置了@ResourceMethod註解的class method,cglib僅支持class 方法切面,接口切面不支持
     * @param proceedingJoinPoint 切面方法實例
     * @param resourceMethod 方法註解實例
     * @return
     * @throws Throwable
     */
    @Around(value = "@annotation(resourceMethod)")
    public Object resourcePutAround(ProceedingJoinPoint proceedingJoinPoint, ResourceMethod resourceMethod)
        throws Throwable
    {
        logger.info("開始處理資源自動設置Aop切面邏輯");
        /**
         * 執行方法,獲取返回值
         */
        Object result = proceedingJoinPoint.proceed();
        if(StringUtils.isEmpty(result)) {return result;}
        /**
         * 返回值爲List集合時
         */
        if(result instanceof List) {
            List<Object> list = (List<Object>) result;
            resourcePushService.push(list);
        }
        /**
         * 返回值爲單值時,返回的實例類型必須繼承BaseEntity
         */
        else if(result instanceof BaseEntity) {
            resourcePushService.push(result);
        }
        logger.info("資源自動設置Aop切面邏輯處理完成.");
        return result;
    }
}
複製代碼

切面環繞方法resourcePutAround大體流程爲:

  1. 執行須要切面的方法,獲取方法結果
  2. 根據方法返回的結果判斷是單個、多個對象進行調用不一樣的方法
  3. 統一資源方法自動根據@ResourceField註解配置信息以及對象類型配置@Id字段的值做爲目標對象編號設置資源到返回對象內。
  4. 返回處理後的對象實例

爲了方便配置咱們在@ResourceField註解內添加了CommonResourceFlag枚舉類型的flag屬性,該屬性就是配置了資源類型的標識,切面會根據該標識去查詢資源的類型編號,再拿着資源類型的編號 & 目標編號去查詢資源列表,CommonResourceFlag枚舉代碼以下所示:

/**
 * 資源標識枚舉
 * ========================
 * Created with IntelliJ IDEA.
 * User:恆宇少年
 * Date:2017/12/31
 * Time:下午3:40
 * 碼雲:http://git.oschina.net/jnyqy
 * ========================
 */
@Getter
public enum CommonResourceFlag
{
    /**
     * 用戶頭像
     */
    USER_HEAD_IMAGE("USER_HEAD_IMAGE"),
    /**
     * 用戶背景圖片
     */
    USER_BACK_IMAGE("USER_BACK_IMAGE");
    private String name;

    CommonResourceFlag(String name) {
        this.name = name;
    }
}
複製代碼

以上咱們簡單介紹了common-resource子模塊的核心內容以及基本的運行流程原理,下面咱們來建立一個user-provider子模塊來使用同一資源查詢用戶的頭像、用戶背景圖片列表。

user-provider

user-provider子模塊目內咱們預計添加一個查詢用戶詳情的方法,在方法上配置@ResourceMethod註解,這樣可讓切面切到該方法,而後在查詢用戶詳情方法返回的對象類型內字段上添加@ResourceField註解並添加對應的資源類型標識配置,這樣咱們就能夠實現資源的自動映射。

因爲該模塊須要數據庫的支持,在application.yml配置文件內添加對應的數據庫連接配置信息,以下所示:

#數據源配置
spring:
  jpa:
    properties:
      hibernate:
        show_sql: true
        format_sql: true
  datasource:
    druid:
      driver-class-name: com.mysql.jdbc.Driver
      username: root
      password: 123456
      url: jdbc:mysql://127.0.0.1:3306/resources?characterEncoding=utf8
複製代碼

配置文件內使用的druidalibaba針對SpringBoot封裝的jar,提供了yml配置文件相關支持以及提示。

用戶實體構建

針對數據庫內的用戶基本信息表咱們須要建立對應的Entity實體,代碼以下所示:

/**
 * 用戶基本信息實體
 * @author yuqiyu
 * ========================
 * Created with IntelliJ IDEA.
 * User:恆宇少年
 * Date:2017/12/31
 * Time:上午11:18
 * 碼雲:http://git.oschina.net/jnyqy
 * ========================
 */
@Data
@Entity
@Table(name = "hy_user_info")
public class UserInfoEntity
    extends BaseEntity
{
    /**
     * 用戶編號
     */
    @Id
    @GeneratedValue(generator = "system-uuid")
    @GenericGenerator(name = "system-uuid", strategy = "uuid")
    @Column(name = "UI_ID")
    private String userId;
    /**
     * 用戶名
     */
    @Column(name = "UI_NAME")
    private String userName;
    /**
     * 暱稱
     */
    @Column(name = "UI_NICK_NAME")
    private String nickName;
    /**
     * 年齡
     */
    @Column(name = "UI_AGE")
    private int age;
    /**
     * 所居地
     */
    @Column(name = "UI_ADDRESS")
    private String address;
}
複製代碼

因爲咱們的用戶頭像以及用戶背景圖片並無在用戶基本信息表內因此咱們須要單首創建一個用戶詳情實體並繼承用戶基本信息實體,以下所示:

/**
 * 用戶詳情dto映射實體
 * @author yuqiyu
 * ========================
 * Created with IntelliJ IDEA.
 * User:恆宇少年
 * Date:2017/12/31
 * Time:上午11:54
 * 碼雲:http://git.oschina.net/jnyqy
 * ========================
 */
@Data
public class UserDetailDTO
    extends UserInfoEntity
{
    /**
     * 用戶頭像
     */
    @ResourceField(flag = CommonResourceFlag.USER_HEAD_IMAGE)
    private String headImage;
    /**
     * 背景圖片
     */
    @ResourceField(flag = CommonResourceFlag.USER_BACK_IMAGE,multiple = true)
    private List<String> backImage;
}
複製代碼

在上面實體內咱們僅僅是配置了字段所需的資源類型枚舉。

咱們通常在開發過程當中,用戶表內對應的實體是不容許根據業務邏輯修改的,若是你須要變更須要繼承實體後添加對應的字段便可。

  • 用戶數據接口
/**
 * 用戶基本信息數據接口
 * @author yuqiyu
 * ========================
 * Created with IntelliJ IDEA.
 * User:恆宇少年
 * Date:2017/12/31
 * Time:上午11:30
 * 碼雲:http://git.oschina.net/jnyqy
 * ========================
 */
public interface UserInfoRepository
    extends JpaRepository<UserInfoEntity,String>
{
    /**
     * 根據用戶名稱查詢
     * @param userName
     * @return
     */
    UserInfoEntity findUserInfoEntityByUserName(String userName);
}
複製代碼
  • 用戶業務邏輯實現
/**
 * 用戶基本信息業務邏輯實現
 *
 * @author yuqiyu
 * ========================
 * Created with IntelliJ IDEA.
 * User:恆宇少年
 * Date:2017/12/31
 * Time:上午11:53
 * 碼雲:http://git.oschina.net/jnyqy
 * ========================
 */
@Service
@Transactional(rollbackFor = Exception.class)
public class UserInfoService {
    /**
     * 用戶數據接口
     */
    @Autowired
    private UserInfoRepository userInfoRepository;

    /**
     * 更新用戶名稱查詢用戶詳情
     * @param userName 用戶名
     * @return
     */
    @ResourceMethod
    public UserDetailDTO selectByUserName(String userName) {
        /**
         * 獲取用戶基本信息
         */
        UserInfoEntity userInfoEntity = userInfoRepository.findUserInfoEntityByUserName(userName);
        /**
         * 經過mapStruct轉換detailDto
         */
        UserDetailDTO detailDTO = UserMapStruct.INSTANCE.fromUserEntity(userInfoEntity);
        return detailDTO;
    }
}
複製代碼

咱們在方法selectByUserName上配置了@ResourceMethod,讓統一資源能夠切面到該方法上,在selectByUserName方法內咱們只須要去處理根據用戶名查詢的業務邏輯,經過MapStruct進行UserInfoEntityUserDetailDTO轉換。在方法返回對象時就會被資源自動處理分別將查詢到的資源設置到UserDetailDTO內的headImagebackImage

  • 用戶控制器 咱們在控制器內添加一個根據用戶名查詢用戶詳情的方法,以下所示:
/**
 * ========================
 * Created with IntelliJ IDEA.
 * User:恆宇少年
 * Date:2017/12/31
 * Time:下午3:09
 * 碼雲:http://git.oschina.net/jnyqy
 * ========================
 */
@RestController
@RequestMapping(value = "/user")
public class UserInfoController
{
    /**
     * 用戶基本信息業務邏輯實現
     */
    @Autowired
    private UserInfoService userInfoService;

    /**
     * 根據用戶名查詢詳情
     * @param userName 用戶名
     * @return
     */
    @RequestMapping(value = "/{userName}",method = RequestMethod.GET)
    public UserDetailDTO detail(@PathVariable("userName") String userName)
    {
        return userInfoService.selectByUserName(userName);
    }
}
複製代碼

下面咱們來編寫一個測試用例,查看是否可以達到咱們預計的效果。

測試

咱們在src/test下建立一個名爲CommonResourceTester測試類,代碼以下所示:

/**
 * 測試用例
 * ========================
 * Created with IntelliJ IDEA.
 * User:恆宇少年
 * Date:2017/12/31
 * Time:下午5:04
 * 碼雲:http://git.oschina.net/jnyqy
 * ========================
 */
@SpringBootTest(classes = Chapter44Application.class)
@RunWith(SpringRunner.class)
public class CommonResourceTester
{
    /**
     * 模擬mvc測試對象
     */
    private MockMvc mockMvc;

    /**
     * web項目上下文
     */
    @Autowired
    private WebApplicationContext webApplicationContext;

    /**
     * 全部測試方法執行以前執行該方法
     */
    @Before
    public void before() {
        //獲取mockmvc對象實例
        mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
    }

    /**
     * 測試查詢用戶詳情
     * @throws Exception
     */
    @Test
    public void selectDetail() throws Exception
    {
        /**
         * 發起獲取請求
         */
        MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/user/hengboy"))
        .andDo(MockMvcResultHandlers.log())
        .andReturn();

        int status = mvcResult.getResponse().getStatus();

        mvcResult.getResponse().setCharacterEncoding("UTF-8");
        String responseString = mvcResult.getResponse().getContentAsString();

        Assert.assertEquals("請求錯誤", 200, status);

        System.out.println(responseString);
    }
}
複製代碼

接下來咱們執行selectDetail測試方法,看下控制檯輸出對應的 JSON內容,格式化後以下所示:

{
    "userId": "bc4c8e38-edd6-11e7-969c-3c15c2e4a8a6", 
    "userName": "hengboy", 
    "nickName": "恆宇少年", 
    "age": 23, 
    "address": "山東省濟南市", 
    "headImage": "https://upload.jianshu.io/users/upload_avatars/4461954/f09ba256-f6db-41ed-a4ac-b2d23737f0ac.jpg?imageMogr2/auto-orient/strip|imageView2/1/w/96/h/96", 
    "backImage": [
        "https://upload.jianshu.io/collections/images/358868/android.graphics.Bitmap_d88b4de.jpeg?imageMogr2/auto-orient/strip|imageView2/1/w/240/h/240", 
        "https://upload.jianshu.io/collections/images/522928/kafka_diagram.png?imageMogr2/auto-orient/strip|imageView2/1/w/240/h/240"
    ]
}
複製代碼

根據結果咱們能夠看到,咱們已經自動的讀取了配置的資源列表,也經過反射自動設置到字段內。

總結

本章的代碼比較多,仍是建議你們根據源碼比對學習,這種方式也是咱們在平時開發中總結出來的,咱們僅僅須要配置下@ResourceField以及@ResourceMethod就能夠了完成資源的自動映射,資源與業務邏輯的耦合度獲得的很好的下降。

本章源碼已經上傳到碼雲: SpringBoot配套源碼地址:gitee.com/hengboy/spr… SpringCloud配套源碼地址:gitee.com/hengboy/spr… SpringBoot相關係列文章請訪問:目錄:SpringBoot學習目錄 QueryDSL相關係列文章請訪問:QueryDSL通用查詢框架學習目錄 SpringDataJPA相關係列文章請訪問:目錄:SpringDataJPA學習目錄,感謝閱讀! 歡迎加入QQ技術交流羣,共同進步。

QQ技術交流羣
相關文章
相關標籤/搜索