【Java项目】好客租房——前台&后台系统

前置知识:ReactJS

  • AntdesignPro搭建后台信息管理系统
  • Semantic-UI前台
  • nodejs模拟前台后端,用于前后端分离开发
  • 后端提供mock数据

AntDesignPro应用

1. 创建工程

2. 导入依赖

tyarn install #安装相关依赖
tyarn start #启动服务

修改logo和footer

【Java项目】好客租房——前台&后台系统_第1张图片

可见,布局是由layout常量定义

logo

可见,左侧的菜单是自定义组件

{isTop && !isMobile ? null : (
    
)}

//导入
import SiderMenu from '@/components/SiderMenu';

打开 /components/SideMenu 文件

return (
      
        
        
      
    );

footer

在Footer.js文件中修改版权信息

import React, { Fragment } from 'react';
import { Layout, Icon } from 'antd';
import GlobalFooter from '@/components/GlobalFooter';

const { Footer } = Layout;
const FooterView = () => (
  
Copyright 2021 Auspice Tian } />
); export default FooterView;

3. 左侧菜单

路由即菜单

修改默认页 router.config.js
【Java项目】好客租房——前台&后台系统_第2张图片

修改国际化映射文件 locale locales=>zh-CN=>settings.js

【Java项目】好客租房——前台&后台系统_第3张图片

只有在路由中的命名空间才会被注册 命名空间名唯一

4. 新增房源

房源表单字段

楼盘数据(estate)

字段 类型 备注
id Long 楼盘id
name String 楼盘名称
province String 所在省
city String 所在市
area String 所在区
address String 具体地址
year String 建筑年代
type String 建筑类型
propertyCost String 物业费
propertyCompany String 物业公司
developers String 开发商
created datetime 创建时间
updated datetime 更新时间

房源数据(houseResource)

字段 类型 备注
id Long 房源id
title String 房源标题,如:南北通透,两室朝南,主卧带阳台
estateId Long 楼盘id
buildingNum String 楼号(栋)
buildingUnit String 单元号
buildingFloorNum String 门牌号
rent int 租金
rentMethod int 租赁方式,1-整租,2-合租
paymentMethod int 支付方式,1-付一押一,2-付三押一,3-付六押一,4-年付押一,5-其它
houseType String 户型,如:2室1厅1卫
coveredArea String 建筑面积
useArea String 使用面积
floor String 楼层,如:8/26
orientation int 朝向:东、南、西、北
decoration String 装修,1-精装,2-简装,3-毛坯
facilities String 配套设施, 如:1,2,3
pic String 图片,最多5张
desc String 房源描述,如:出小区门,门口有时代联华超市,餐饮有川菜馆,淮南牛肉汤,黄焖鸡沙县小吃等;可到达亲水湾城市生活广场,里面有儿童乐园,台球室和康桥健身等休闲娱乐;生活广场往北沿御水路往北步行一公里就是御桥路,旁边就是御桥地铁站,地铁站商场…
contact String 联系人
mobile String 手机号
time int 看房时间,1-上午,2-中午,3-下午,4-晚上,5-全天
propertyCost String 物业费
created datetime 创建时间
updated datetime 更新时间

antd表单组件

官网链接

高性能表单控件,自带数据域管理。包含数据录入校验 以及对应 样式API

API

被设置了 name 属性的 Form.Item 包装的控件,表单控件会自动添加 value(或 valuePropName 指定的其他属性) onChange(或 trigger 指定的其他属性),数据同步将被 Form 接管,这会导致以下结果:

  1. 不再需要也不应该onChange 来做数据收集同步(你可以使用 Form 的 onValuesChange),但还是可以继续监听 onChange 事件。
  2. 你不能用控件的 valuedefaultValue 等属性来设置表单域的值,默认值可以用 Form 里的 initialValues 来设置。注意 initialValues 不能被 setState 动态更新,你需要用 setFieldsValue 来更新。
  3. 你不应该用 setState,可以使用 form.setFieldsValue 来动态改变表单值。

在 rules的参数中,可以增加校验规则

{
	initialValue:'1',
	rules:[{ 
		required: true, 
		message:"此项为必填项" 
	}]
}

表单提交

表单的提交通过submit按钮完成,通过onSubmit方法进行拦截处理


    
    

handleSubmit = e => {
    const { dispatch, form } = this.props;
    e.preventDefault();
    console.log(this.state.fileList);
    form.validateFieldsAndScroll((err, values) => {
        
        if (!err) {
            //对设施进行处理
            //1,2,3,4
            //水,电,煤气/天然气,暖气
            if(values.facilities){
                values.facilities = values.facilities.join(",");
            }
            // 3/20
            // 第三层、总共20层
            if(values.floor_1 && values.floor_2){
                values.floor = values.floor_1 + "/" + values.floor_2;
            }
		
            //3室1厅2卫1厨有阳台
            values.houseType = values.houseType_1 + "室" + values.houseType_2 + "厅"
                + values.houseType_3 + "卫" + values.houseType_4 + "厨"
                + values.houseType_2 + "阳台";
            delete values.floor_1;
            delete values.floor_2;
            delete values.houseType_1;
            delete values.houseType_2;
            delete values.houseType_3;
            delete values.houseType_4;
            delete values.houseType_5;

            dispatch({
                type: 'form/submitRegularForm',
                payload: values,
            });
        }
    });
};

【Java项目】好客租房——前台&后台系统_第4张图片

自动填充

文档

效果

【Java项目】好客租房——前台&后台系统_第5张图片

【Java项目】好客租房——前台&后台系统_第6张图片

实现

{
        let v = estateMap.get(value);
        this.setState({
            estateAddress: v.substring(v.indexOf('|')+1),
            estateId : v.substring(0,v.indexOf('|'))
        });
    }}
    onSearch={this.handleSearch}
    filterOption={(inputValue, option) => option.props.children.toUpperCase().indexOf(inputValue.toUpperCase()) !== -1}
    />

const estateMap = new Map([
  ['中远两湾城','1001|上海市,上海市,普陀区,远景路97弄'],
  ['上海康城','1002|上海市,上海市,闵行区,莘松路958弄'],
  ['保利西子湾','1003|上海市,上海市,松江区,广富林路1188弄'],
  ['万科城市花园','1004|上海市,上海市,闵行区,七莘路3333弄2区-15区'],
  ['上海阳城','1005|上海市,上海市,闵行区,罗锦路888弄']
]);

// 通过onSearch进行动态设置数据源,这里使用的数据是静态数据
handleSearch = (value)=>{
	let arr = new Array();
	if(value.length > 0 ){
		estateMap.forEach((v, k) => {
			if(k.startsWith(value)){
                arr.push(k);
            }
        });
	}
    this.setState({
        estateDataSource: arr
    });
} ;

// 通过onSelect设置,选择中楼盘数据后,在楼盘地址中填写地址数据
onSelect={(value, option)=>{
	let v = estateMap.get(value);
	this.setState({
        estateAddress: v.substring(v.indexOf('|')+1),
        estateId : v.substring(0,v.indexOf('|'))
	});
}}

5. 图片上传组件

图片上传通过自定义组件 PicturesWall 完成,在PictureWall中,通过 antd 的 Upload 组件实现

如何解决子组件的值传递到父组件

【Java项目】好客租房——前台&后台系统_第7张图片

  • bind方法可以将子组件(PicturesWall)中的this指向父组件(HousingAdd)的this
    • 在子组件中调用父组件的方法相当于在父组件的上下文中调用该方法,所以该函数的参数在父组件的上下文中也可以获取到
  • 父组件通过属性的方式进行引用子组件 ,在子组件中,通过 this.props 获取传入的函数,进行调用,即可将数据传递到父组件中

this——函数执行时上下文

this 的值是在执行的时候才能确认,定义的时候不能确认!

this 是执行上下文环境的一部分,而执行上下文需要在代码执行之前确定,而不是定义的时候

var obj = {
    getThis: function() {
        console.log(this);
    }
};

obj.getThis();

var getThisCopy = obj.getThis;

getThisCopy();

【Java项目】好客租房——前台&后台系统_第8张图片

bind

绑定函数,使其无论怎么样调用都用相同的上下文环境

fun.bind(thisArgument, argument1, argument2, …)

  • thisArgument:在 fun 函数运行时的 this 值,如果绑定函数时使用 new 运算符构造的,则该值将被忽略。
var obj = {
    num: 100,
    numFun: function() {
        console.log(this.num);
    }
};

var numFunCopy = obj.numFun;

numFunCopy();

在这里插入图片描述

Window 上下文中,没有 num 值,num的值是在 obj 中定义的

所以引入 bind() 解决 this 不能够指向原来的问题

var obj = {
    num: 100,
    numFun: function(){
        console.log(this.num);
    }

}

var numFunCopy = obj.numFun;

obj.numFun();

numFunCopy.bind(obj)();

【Java项目】好客租房——前台&后台系统_第9张图片

前台

前端是使用React+semantic-ui实现移动端web展示,后期可以将web打包成app进行发布

1. 搭建工程

npm install # 安装依赖
npm start # 启动服务

地址:http://localhost:9000/

【Java项目】好客租房——前台&后台系统_第10张图片

2. 搭建api工程

使用node.js开发服务端的方式进行了demo化开发,只是作为前端开发的api工程,并不是实际环境

  1. 创建数据库

    将 myhome.sql 执行 ,创建数据库

  2. 修改配置文件——数据库配置

    /** 数据库配置 */
    db: {
        /** 模型文件路径 */
        models*path: '/models',
        /** 数据库主机IP */
        host: '8.140.130.91',
        /** 数据库的端口号 */
        port: 3306,
        /** 数据库类型 */
        type: 'mysql',
        /** 数据库登录用户名 */
        username: 'root',
        /** 数据库密码 */
        password: 'root',
        /** 数据库名称 */
        database: 'myhome',
        /** 是否显示数据库日志 */
        logging: console.log,// false 为禁用日志
        /** 配置数据库连接池 */
        pool: {
            max: 5,
            min: 0,
            charset: 'utf8',
            idle: 30000
        }
    }
    
  3. 输入命令进行初始化和启动服务

    npm install #安装依赖
    npm run dev #启动dev脚本
    
    #脚本如下
    "scripts": {
        "test": "cross-env NODE*ENV=config-test node app.js",
        "dev": "cross-env NODE*ENV=config-dev node app.js", #设置环境变量
        "pro": "cross-env NODE*ENV=config-pro node app.js"
    }
    
  4. 登录系统测试

    问题

    • Client does not support authentication protocol requested by server; conside

      use mysql;
      
      flush privileges;
      
      -- 加密算法为caching*sha2*password,而旧版加密算法为mysql*native*password
      select user,host,plugin from user; 
      
      alter user 'root'@'%' identified with mysql*native*password by 'root';
      
      select user,host,plugin from user;
      
    • ERWRONGFIELDWITHGROUP

      use myhome;
      
      SET sql*mode=(SELECT REPLACE(@@sql*mode, 'ONLY*FULL*GROUP*BY', ''));
      
      select @@sql*mode;
      

在这里插入图片描述
【Java项目】好客租房——前台&后台系统_第11张图片

3. 前台实现分析

React APP目录结构

【Java项目】好客租房——前台&后台系统_第12张图片

加载数据流程

【Java项目】好客租房——前台&后台系统_第13张图片

  • Promise.all()方法获取到所有的异步处理的结果,并且将结果保存到this.state中,然后再render中渲染

  • app.js

    //设置全局的  axios baseUrl 配置
    axios.defaults.baseURL = config.apiBaseUrl;
    //设置拦截器
    axios.interceptors.request.use(function (config) {
      //在发送请求前获取mytoken的值
      if(!config.url.endsWith('/login')){
        config.headers.Authorization = localStorage.getItem('mytoken');
      }
      return config;
    }, function (error) {
      //获取数据失败处理
      return Promise.reject(error);
    });
    axios.interceptors.response.use(function (response) {
      // 对响应的拦截——————返回response.data数据
      return response.data;
    }, function (error) {
      return Promise.reject(error);
    });
    

伪mock服务

目标:所有的数据通过自己实现的接口提供,不需要使用nodejs,便于后端开发

1. 构造数据

mock-data.properties

mock.indexMenu={"data":{"list":[\
  {"id":1,"menu*name":"二手房","menu*logo":"home","menu*path":"/home","menu*status":1,"menu*style":null},\
  {"id":2,"menu*name":"新房","menu*logo":null,"menu*path":null,"menu*status":null,"menu*style":null},\
  {"id":3,"menu*name":"租房","menu*logo":null,"menu*path":null,"menu*status":null,"menu*style":null},\
  {"id":4,"menu*name":"海外","menu*logo":null,"menu*path":null,"menu*status":null,"menu*style":null},\
  {"id":5,"menu*name":"地图找房","menu*logo":null,"menu*path":null,"menu*status":null,"menu*style":null},\
  {"id":6,"menu*name":"查公交","menu*logo":null,"menu*path":null,"menu*status":null,"menu*style":null},\
  {"id":7,"menu*name":"计算器","menu*logo":null,"menu*path":null,"menu*status":null,"menu*style":null},\
  {"id":8,"menu*name":"问答","menu*logo":null,"menu*path":null,"menu*status":null,"menu*style":null}]},"meta":\
  {"status":200,"msg":"测试数据"}}

mock.indexInfo={"data":{"list":[\
  {"id":1,"info*title":"房企半年销售业绩继","info*thumb":null,"info*time":null,"info*content":null,"user*id":null,"info*status":null,"info*type":1},\
  {"id":2,"info*title":"上半年土地市场两重天:一线降温三四线量价齐升","info*thumb":null,"info*time":null,"info*content":null,"user*id":null,"info*status":null,"info*type":1}]},\
  "meta":{"status":200,"msg":"测试数据"}}

mock.indexFaq={"data":{"list":[\
  {"question*name":"在北京买房,需要支付的税费有哪些?","question*tag":"学区,海淀","answer*content":"各种费用","atime":33,"question*id":1,"qnum":2},\
  {"question*name":"一般首付之后,贷款多久可以下来?","question*tag":"学区,昌平","answer*content":"大概1个月","atime":22,"question*id":2,"qnum":2}]},\
  "meta":{"status":200,"msg":"测试数据"}}

mock.indexHouse={"data":{"list":[\
  {"id":1,"home*name":"安贞西里123","home*price":"4511","home*desc":"72.32㎡/南 北/低楼层","home*infos":null,"home*type":1,"home*tags":"海淀,昌平","home*address":null,"user*id":null,"home*status":null,"home*time":12,"group*id":1},\
  {"id":8,"home*name":"安贞西里 三室一厅","home*price":"4500","home*desc":"72.32㎡/南北/低楼层","home*infos":null,"home*type":1,"home*tags":"海淀","home*address":null,"user*id":null,"home*status":null,"home*time":23,"group*id":2},\
  {"id":3,"home*name":"安贞西里 三室一厅","home*price":"4220","home*desc":"72.32㎡/南北/低楼层","home*infos":null,"home*type":2,"home*tags":"海淀","home*address":null,"user*id":null,"home*status":null,"home*time":1,"group*id":1},\
  {"id":4,"home*name":"安贞西里 三室一厅","home*price":"4500","home*desc":"72.32㎡/南 北/低楼层","home*infos":"4500","home*type":2,"home*tags":"海淀","home*address":"","user*id":null,"home*status":null,"home*time":12,"group*id":2},\
  {"id":5,"home*name":"安贞西里 三室一厅","home*price":"4522","home*desc":"72.32㎡/南 北/低楼层","home*infos":null,"home*type":3,"home*tags":"海淀","home*address":null,"user*id":null,"home*status":null,"home*time":23,"group*id":1},\
  {"id":6,"home*name":"安贞西里 三室一厅","home*price":"4500","home*desc":"72.32㎡/南北/低楼层","home*infos":null,"home*type":3,"home*tags":"海淀","home*address":null,"user*id":null,"home*status":null,"home*time":1221,"group*id":2},\
  {"id":9,"home*name":"安贞西里 三室一厅","home*price":"4500","home*desc":"72.32㎡/南北/低楼层","home*infos":null,"home*type":4,"home*tags":"海淀","home*address":null,"user*id":null,"home*status":null,"home*time":23,"group*id":1}\
  ]},
"meta":{"status":200,"msg":"测试数据"}}

mock.infosList1={"data":{"list":{"total":8,"data":[{"id":13,"info*title":"wwwwwwwwwwwww","info*thumb":null,"info*time":null,"info*content":null,"user*id":null,"info*status":null,"info*type":1},{"id":12,"info*title":"房企半年销售业绩继","info*thumb":null,"info*time":null,"info*content":null,"user*id":null,"info*status":null,"info*type":1}]}},"meta":{"status":200,"msg":"获取数据成功"}}
mock.infosList2={"data":{"list":{"total":4,"data":[{"id":9,"info*title":"房企半年销售业绩继续冲高三巨头销售额过亿","info*thumb":null,"info*time":null,"info*content":null,"user*id":null,"info*status":null,"info*type":2},{"id":7,"info*title":"房企半年销售业绩继续冲高三巨头销售额过亿","info*thumb":null,"info*time":null,"info*content":null,"user*id":null,"info*status":null,"info*type":2}]}},"meta":{"status":200,"msg":"获取数据成功"}}
mock.infosList3={"data":{"list":{"total":10,"data":[{"username":"tom","question*name":"在北京买房,需要支付的税费有哪些?","question*tag":"学区,海淀","answer*content":"各种费用","atime":33,"question*id":1,"qnum":2},{"username":"tom","question*name":"一般首付之后,贷款多久可以下来?","question*tag":"学区,昌平","answer*content":"大概1个月","atime":22,"question*id":2,"qnum":2}]}},"meta":{"status":200,"msg":"获取数据成功"}}

mock.my={"data":{"id":1,"username":"tom","password":"123","mobile":"123","type":null,"status":null,"avatar":"public/icon.png"},"meta":{"status":200,"msg":"获取数据成功"}}

2. 创建MockConfig

读取properties文件,映射为String

package com.haoke.api.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

@PropertySource("classpath:mock-data.properties")
@ConfigurationProperties(prefix = "mock")
@Configuration
@Data
public class MockConfig {
    private String indexMenu;
    private String indexInfo;
    private String indexFaq;
    private String indexHouse;
    private String infosList1;
    private String infosList2;
    private String infosList3;
    private String my;
}

3. MockController

package com.haoke.api.controller;

import com.haoke.api.config.MockConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RequestMapping("mock")
@RestController
@CrossOrigin
public class MockController {

    @Autowired
    private MockConfig mockConfig;

    /**
     * 菜单
     *
     * @return
     */
    @GetMapping("index/menu")
    public String indexMenu() {
        return this.mockConfig.getIndexMenu();
    }

    /**
     * 首页资讯
     * @return
     */
    @GetMapping("index/info")
    public String indexInfo() {
        return this.mockConfig.getIndexInfo();
    }

    /**
     * 首页问答
     * @return
     */
    @GetMapping("index/faq")
    public String indexFaq() {
        return this.mockConfig.getIndexFaq();
    }

    /**
     * 首页房源信息
     * @return
     */
    @GetMapping("index/house")
    public String indexHouse() {
        return this.mockConfig.getIndexHouse();
    }

    /**
     * 查询资讯
     *
     * @param type
     * @return
     */
    @GetMapping("infos/list")
    public String infosList(@RequestParam("type")Integer type) {
        switch (type){
            case 1:
                return this.mockConfig.getInfosList1();
            case 2:
                return this.mockConfig.getInfosList2();
            case 3:
                return this.mockConfig.getInfosList3();
        }
        return this.mockConfig.getInfosList1();
    }

    /**
     * 我的中心
     * @return
     */
    @GetMapping("my/info")
    public String myInfo() {
        return this.mockConfig.getMy();
    }
}

4. 测试

【Java项目】好客租房——前台&后台系统_第14张图片

5. 整合前端

【Java项目】好客租房——前台&后台系统_第15张图片

axios

Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。

  • 从浏览器中创建 XMLHttpRequests
  • 从 node.js 创建 http 请求
  • 支持 Promise API
  • 拦截请求和响应
  • 转换请求数据和响应数据
  • 取消请求
  • 自动转换 JSON 数据
  • 客户端支持防御

title: 毕设项目后台
top: 63
categories:

  • 开发
  • Java项目
  • 毕设项目
    tags:
  • 开发
  • Java项目
  • 毕设项目
    abbrlink: 1207943057
    date: 2022-03-18 16:41:48
    updated: 2022-4-1 18:39:38

前置知识:

Mybatis

Spring

SpringMVC

SpringBoot

微服务

Dubbo

后台信息管理系统采用的是前后端分离开发模式,前端使用LayUI系统作为模板进行改造,后端采用的是SpringBoot+Dubbo+SSM的架构进行开发

  • 后台系统架构
  • 工具:图像存储——COS对象存储
  • 分析服务之间关系并创建工程
  • 代码优化:
    • 抽取公共工程
    • GraphQL风格查询接口

后台系统

后台系统架构

后台服务采用微服务的思想,使用Dubbo作为服务治理框架

【Java项目】好客租房——前台&后台系统_第16张图片

微服务架构

在单体架构的应用中,一个服务的所有功能模块都会被部署到同一台机器上。当用户规模变得庞大,应用的响应速度会变慢。这个时候可以通过增加一台机器部署应用来提高响应速度。假设这个应用只有房源模块和用户模块。经过分析,这个系统中房源模块消耗的资源最多。提高响应速度最高效的方法就是单独为房源模块分配更多资源。单体架构的应用无法做到这一点。

在微服务架构的应用中,应用会按照功能模块拆分为多个服务。部署时,可以在一台机器上部署一个用户服务和一个房源服务,另一台机器上部署两个房源服务,这样可以最大化利用系统资源。

在该项目中为什么引入Dubbo

Dubbo框架基于生产者-消费者模型来完成服务治理。在一项业务流程中,一个服务要么是服务提供方,要么是服务消费方。降低了各层之间服务的耦合度。

设想一种场景,前台使用者进入程序会根据地理位置、平时浏览的户型、设置的喜好推送房源列表,后台管理员登录管理系统后,也会有获取房源列表的需求。但这两种角色身份不同,获取到的房源列表也会不同,所以需要不同的处理器对模型层返回的房源列表进行处理。但这两种处理器中,从模型层获取房源列表这个过程是公有的,所以将模型层查询房源列表服务作为一个服务,提供给不同控制器调用。

这与Dubbo的生产者-消费者模式很类似,Service层处理完数据后提供给Controller层,Controller层利用Service层的数据进行不同的业务处理流程,所以我把Service层作为服务的生产者,Controller层作为服务的消费者。

项目结构

【Java项目】好客租房——前台&后台系统_第17张图片

父工程

haoke-manage


<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">
    <modelVersion>4.0.0modelVersion>

    <groupId>com.haoke.managegroupId>
    <artifactId>haoke-manageartifactId>
    <packaging>pompackaging>
    <version>1.0-SNAPSHOTversion>
    <modules>
        <module>haoke-manage-dubbo-servermodule>
        <module>haoke-manage-api-servermodule>
    modules>

    
    <parent>
        <artifactId>spring-boot-starter-parentartifactId>
        <groupId>org.springframework.bootgroupId>
        <version>2.4.3version>
    parent>

    <dependencies>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <version>2.4.3version>
        dependency>
        <dependency>
            <groupId>org.apache.commonsgroupId>
            <artifactId>commons-lang3artifactId>
        dependency>

        
        <dependency>
            <groupId>com.alibaba.bootgroupId>
            <artifactId>dubbo-spring-boot-starterartifactId>
            <version>0.2.0version>
        dependency>
        
        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>dubboartifactId>
            <version>2.6.4version>
        dependency>

        
        <dependency>
            <groupId>org.apache.zookeepergroupId>
            <artifactId>zookeeperartifactId>
            <version>3.4.13version>
        dependency>
        <dependency>
            <groupId>com.github.sgroschupfgroupId>
            <artifactId>zkclientartifactId>
            <version>0.1version>
        dependency>
    dependencies>

    <build>
        <plugins>
            
            <plugin>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-maven-pluginartifactId>
            plugin>
        plugins>
    build>
project>

子工程

服务生产方

haoke-manage-dubbo-server


<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>haoke-manageartifactId>
        <groupId>com.haoke.managegroupId>
        <version>1.0-SNAPSHOTversion>
    parent>
    <modelVersion>4.0.0modelVersion>

    <packaging>pompackaging>
    <modules>
        <module>haoke-manage-dubbo-server-house-resourcesmodule>
        <module>haoke-manage-dubbo-server-generatormodule>
    modules>

    <artifactId>haoke-manage-dubbo-serverartifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starterartifactId>
        dependency>

        
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
        dependency>
    dependencies>
project>

服务消费方

haoke-manage-api-server


<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>haoke-manageartifactId>
        <groupId>com.haoke.managegroupId>
        <version>1.0-SNAPSHOTversion>
    parent>
    <modelVersion>4.0.0modelVersion>

    <artifactId>haoke-manage-api-serverartifactId>

    <dependencies>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        
        <dependency>
            <groupId>com.haoke.managegroupId>
            <artifactId>haoke-manage-dubbo-server-house-resources-interfaceartifactId>
            <version>1.0-SNAPSHOTversion>
        dependency>
    dependencies>
project>

房源服务的构建

创建数据表

use haoke;

DROP TABLE IF EXISTS `TB_ESTATE`;

CREATE TABLE `TB_ESTATE` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `name` varchar(100) DEFAULT NULL COMMENT '楼盘名称',
  `province` varchar(10) DEFAULT NULL COMMENT '所在省',
  `city` varchar(10) DEFAULT NULL COMMENT '所在市',
  `area` varchar(10) DEFAULT NULL COMMENT '所在区',
  `address` varchar(100) DEFAULT NULL COMMENT '具体地址',
  `year` varchar(10) DEFAULT NULL COMMENT '建筑年代',
  `type` varchar(10) DEFAULT NULL COMMENT '建筑类型',
  `property_cost` varchar(10) DEFAULT NULL COMMENT '物业费',
  `property_company` varchar(20) DEFAULT NULL COMMENT '物业公司',
  `developers` varchar(20) DEFAULT NULL COMMENT '开发商',
  `created` datetime DEFAULT NULL COMMENT '创建时间',
  `updated` datetime DEFAULT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1006 DEFAULT CHARSET=utf8 COMMENT='楼盘表';

INSERT INTO `TB_ESTATE` VALUES 
(1001,'中远两湾城','上海市','上海市','普陀区','远景路97弄','2001','塔楼/板楼','1.5','上海中远物业管理发展有限公司','上海万业企业股份有限公司','2021-03-16 23:00:20','2021-03-16 23:00:20'),
(1002,'上海康城','上海市','上海市','闵行区','莘松路958弄','2001','塔楼/板楼','1.5','盛孚物业','闵行房地产','2021-03-16 23:00:20','2021-03-16 23:00:20'),
(1003,'保利西子湾','上海市','上海市','松江区','广富林路1188弄','2008','塔楼/板楼','1.75','上海保利物业管理','上海城乾房地产开发有限公司','2021-03-16 23:00:20','2021-03-16 23:00:20'),
(1004,'万科城市花园','上海市','上海市','松江区','广富林路1188弄','2002','塔楼/板楼','1.5','上海保利物业管理','上海城乾房地产开发有限公司','2021-03-16 23:00:20','2021-03-16 23:00:20'),
(1005,'上海阳城','上海市','上海市','闵行区','罗锦路888弄','2002','塔楼/板楼','1.5','上海莲阳物业管理有限公司','上海莲城房地产开发有限公司','2021-03-16 23:00:20','2021-03-16 23:00:20');


CREATE TABLE `TB_HOUSE_RESOURCES` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `title` varchar(100) DEFAULT NULL COMMENT '房源标题',
  `estate_id` bigint(20) DEFAULT NULL COMMENT '楼盘id',
  `building_num` varchar(5) DEFAULT NULL COMMENT '楼号(栋)',
  `building_unit` varchar(5) DEFAULT NULL COMMENT '单元号',
  `building_floor_num` varchar(5) DEFAULT NULL COMMENT '门牌号',
  `rent` int(10) DEFAULT NULL COMMENT '租金',
  `rent_method` tinyint(1) DEFAULT NULL COMMENT '租赁方式,1-整租,2-合租',
  `payment_method` tinyint(1) DEFAULT NULL COMMENT '支付方式,1-付一押一,2-付三押一,3-付六押一,4-年付押一,5-其它',
  `house_type` varchar(255) DEFAULT NULL COMMENT '户型,如:2室1厅1卫',
  `covered_area` varchar(10) DEFAULT NULL COMMENT '建筑面积',
  `use_area` varchar(10) DEFAULT NULL COMMENT '使用面积',
  `floor` varchar(10) DEFAULT NULL COMMENT '楼层,如:8/26',
  `orientation` varchar(2) DEFAULT NULL COMMENT '朝向:东、南、西、北',
  `decoration` tinyint(1) DEFAULT NULL COMMENT '装修,1-精装,2-简装,3-毛坯',
  `facilities` varchar(50) DEFAULT NULL COMMENT '配套设施, 如:1,2,3',
  `pic` varchar(200) DEFAULT NULL COMMENT '图片,最多5张',
  `house_desc` varchar(200) DEFAULT NULL COMMENT '描述',
  `contact` varchar(10) DEFAULT NULL COMMENT '联系人',
  `mobile` varchar(11) DEFAULT NULL COMMENT '手机号',
  `time` tinyint(1) DEFAULT NULL COMMENT '看房时间,1-上午,2-中午,3-下午,4-晚上,5-全天',
  `property_cost` varchar(10) DEFAULT NULL COMMENT '物业费',
  `created` datetime DEFAULT NULL,
  `updated` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='房源表';

POJO

BasePOJO

package com.haoke.dubbo.server.pojo;

@Data
public abstract class BasePojo implements Serializable {
    private Date created;
    private Date updated;
}

房源POJO

1. MybatisPlus逆向工程生成POJO

mybatis-plus的AutoGenerator插件根据 数据库中的表结构 生成相应的POJO类

创建generator项目

【Java项目】好客租房——前台&后台系统_第18张图片


<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">

    <modelVersion>4.0.0modelVersion>
    <artifactId>haoke-manage-dubbo-server-generatorartifactId>

    <dependencies>
        
        <dependency>
            <groupId>org.freemarkergroupId>
            <artifactId>freemarkerartifactId>
        dependency>

        
        <dependency>
            <groupId>com.baomidougroupId>
            <artifactId>mybatis-plus-coreartifactId>
            <version>3.4.2version>
        dependency>
        <dependency>
            <groupId>com.baomidougroupId>
            <artifactId>mybatis-plus-generatorartifactId>
            <version>3.4.1version>
        dependency>
    dependencies>
project>
编写CodeGenerator
public class CodeGenerator {
    /**
     * 读取控制台内容
     */
    public static String scanner(String tip) {
        Scanner scanner = new Scanner(System.in);
        StringBuilder help = new StringBuilder();
        
        help.append("请输入" + tip + ":");
        System.out.println(help.toString());
        
        if (scanner.hasNext()) {
            String ipt = scanner.next();
            if (StringUtils.isNotEmpty(ipt)) {
                return ipt;
            }
        }
        throw new MybatisPlusException("请输入正确的" + tip + "!");
    }

    public static void main(String[] args) {
        // 代码生成器
        AutoGenerator mpg = new AutoGenerator();

        // 全局配置
        GlobalConfig gc = new GlobalConfig();
        String projectPath = System.getProperty("user.dir");
        gc.setOutputDir(projectPath + "/src/main/java");
        gc.setAuthor("amostian");
        gc.setOpen(false);
        mpg.setGlobalConfig(gc);

        // 数据源配置
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setUrl("jdbc:mysql://82.157.25.57:4002/haoke?characterEncoding=utf8&useSSL=false&serverTimezone=UTC");
        // dsc.setSchemaName("public");
        dsc.setDriverName("com.mysql.cj.jdbc.Driver");
        dsc.setUsername("mycat");
        dsc.setPassword("mycat");
        mpg.setDataSource(dsc);

        // 目标包配置
        PackageConfig pc = new PackageConfig();
        pc.setModuleName(scanner("模块名"));
        pc.setParent("com.haoke.dubbo.server");
        mpg.setPackageInfo(pc);

        // 自定义配置
        InjectionConfig cfg = new InjectionConfig(){
            @Override
            public void initMap() {

            }
        };
        List<FileOutConfig> focList = new ArrayList<>();
        focList.add(new FileOutConfig("/templates/mapper.xml.ftl") {
            @Override
            public String outputFile(TableInfo tableInfo) {
                // 自定义输入文件名称
                return projectPath + "/src/main/resources/mapper/" +
                    pc.getModuleName()
                    + "/" + tableInfo.getEntityName() + "Mapper" +
                    StringPool.DOT_XML;
            }
        });
        cfg.setFileOutConfigList(focList);
        mpg.setCfg(cfg);
        mpg.setTemplate(new TemplateConfig().setXml(null));

        // 策略配置
        StrategyConfig strategy = new StrategyConfig();
        strategy.setNaming(NamingStrategy.underline_to_camel);
        strategy.setColumnNaming(NamingStrategy.underline_to_camel);
        strategy.setSuperEntityClass("com.haoke.dubbo.server.pojo.BasePojo");
        strategy.setEntityLombokModel(true);
        strategy.setRestControllerStyle(true);
        strategy.setInclude(scanner("表名"));
        strategy.setSuperEntityColumns("id");
        strategy.setControllerMappingHyphenStyle(true);
        strategy.setTablePrefix(pc.getModuleName() + "_");
        
        mpg.setStrategy(strategy);
        mpg.setTemplateEngine(new FreemarkerTemplateEngine());
        mpg.execute();
    }
}
运行代码

【Java项目】好客租房——前台&后台系统_第19张图片
【Java项目】好客租房——前台&后台系统_第20张图片

只需要entity (pojo)

  • @EqualsAndHashCode(callSuper = true)
    • 自动生成equals和 hashcode 方法,一般不需要,所以去掉
  • @Accessors(chain = true)
    • 表示,生成的set方法将采用链式编程方式
2. 将生成的POJO放到 com.haoke.dubbo.server.pojo
package com.haoke.dubbo.server.pojo;

import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;

@Data
@EqualsAndHashCode(callSuper = true)
@TableName("TB_HOUSE_RESOURCES")
public class HouseResources extends BasePojo {
    
    private static final long serialVersionUID = -2471649692631014216L;
    
    /**
     * 房源标题
     */
    private String title;

    /**
     * 楼盘id
     */
    @TableId(value = "ID", type = IdType.AUTO)
    private Long estateId;

    /**
     * 楼号(栋)
     */
    private String buildingNum;

    /**
     * 单元号
     */
    private String buildingUnit;

    /**
     * 门牌号
     */
    private String buildingFloorNum;

    /**
     * 租金
     */
    private Integer rent;

    /**
     * 租赁方式,1-整租,2-合租
     */
    private Integer rentMethod;

    /**
     * 支付方式,1-付一押一,2-付三押一,3-付六押一,4-年付押一,5-其它
     */
    private Integer paymentMethod;

    /**
     * 户型,如:2室1厅1卫
     */
    private String houseType;

    /**
     * 建筑面积
     */
    private String coveredArea;

    /**
     * 使用面积
     */
    private String useArea;

    /**
     * 楼层,如:8/26
     */
    private String floor;

    /**
     * 朝向:东、南、西、北
     */
    private String orientation;

    /**
     * 装修,1-精装,2-简装,3-毛坯
     */
    private Integer decoration;

    /**
     * 配套设施, 如:1,2,3
     */
    private String facilities;

    /**
     * 图片,最多5张
     */
    private String pic;

    /**
     * 描述
     */
    private String houseDesc;

    /**
     * 联系人
     */
    private String contact;

    /**
     * 手机号
     */
    private String mobile;

    /**
     * 看房时间,1-上午,2-中午,3-下午,4-晚上,5-全天
     */
    private Integer time;

    /**
     * 物业费
     */
    private String propertyCost;
}

房源服务项目结构

【Java项目】好客租房——前台&后台系统_第21张图片

将房源业务分为接口层面和实现层面,是为了便于组件化维护。

  • 实现层面是Spring业务,具体实现业务逻辑。
  • 接口层面作为Dubbo的服务导出。

haoke-manage-dubbo-server-house-resources


<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>haoke-manage-dubbo-serverartifactId>
        <groupId>com.haoke.managegroupId>
        <version>1.0-SNAPSHOTversion>
    parent>
    <modelVersion>4.0.0modelVersion>

    <artifactId>haoke-manage-dubbo-server-house-resourcesartifactId>
    <packaging>pompackaging>
    
    <modules>
        <module>haoke-manage-dubbo-server-house-resources-interfacemodule>
        <module>haoke-manage-dubbo-server-house-resources-servicemodule>
    modules>

    <dependencies>
        <dependency>
            <groupId>com.baomidougroupId>
            <artifactId>mybatis-plus-boot-starterartifactId>
            <version>3.4.2version>
        dependency>
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
            <version>8.0.16version>
        dependency>
    dependencies>
project>

房源业务接口

haoke-manage-dubbo-server-house-resources-interface

  • 对外提供的SDK包
  • 只提供pojo实体类以及接口

<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>haoke-manage-dubbo-server-house-resourcesartifactId>
        <groupId>com.haoke.managegroupId>
        <version>1.0-SNAPSHOTversion>
    parent>
    <modelVersion>4.0.0modelVersion>

    <artifactId>haoke-manage-dubbo-server-house-resources-interfaceartifactId>
project>

房源业务实现

haoke-manage-server-house-resources-service

房源服务的实现——Spring业务


<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>haoke-manage-dubbo-server-house-resourcesartifactId>
        <groupId>com.haoke.managegroupId>
        <version>1.0-SNAPSHOTversion>
    parent>
    <modelVersion>4.0.0modelVersion>

    <artifactId>haoke-manage-dubbo-server-house-resources-serviceartifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-jdbcartifactId>
        dependency>
        <dependency>
            <groupId>com.haoke.managegroupId>
            <artifactId>haoke-manage-dubbo-server-house-resources-interfaceartifactId>
            <version>1.0-SNAPSHOTversion>
        dependency>
    dependencies>
project>

相关配置

application.preperties

# Spring boot application
spring.application.name = haoke-manage-dubbo-server-house-resources

# 数据库
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://82.157.25.25:4002/haoke?characterEncoding=utf8&useSSL=false&serverTimezone=UTC
spring.datasource.username=mycat
spring.datasource.password=mycat

# Dubbo配置
##服务的扫描包
dubbo.scan.basePackages = com.haoke.server.api
##服务名称
dubbo.application.name = dubbo-provider-house-resources
dubbo.service.version = 1.0.0
##协议以及端口
dubbo.protocol.name = dubbo
dubbo.protocol.port = 20880
##zk服务注册中心地址
dubbo.registry.address = zookeeper://8.140.130.91:2181
dubbo.registry.client = zkclient

新增房源

服务提供方

Dubbo服务定义

haoke-manage-dubbo-server-house-resources-interface

package com.haoke.server.api;
import com.haoke.server.pojo.HouseResources;

public interface ApiHouseResourcesService {
    /**
     * @param houseResources
     *
     * @return -1:输入的参数不符合要求,0:数据插入数据库失败,1:成功
     */
    int saveHouseResources(HouseResources houseResources);
}
新增房源业务实现

创建SpringBoot应用,实现新增房源服务

  1. 连接数据库——Dao层
  2. 实现CRUD接口——Service层

在这里插入图片描述

Dao层

MybatisPlus配置类

package com.haoke.server.config;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;

//定义包扫描路径
@MapperScan("com.haoke.server.mapper")
@Configuration
public class MybatisPlusConfig {}

HouseResourcesMapper接口

package com.haoke.server.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.haoke.dubbo.server.pojo.HouseResources;

//BaseMapper是MybatisPlus提供的基本CRUD类
public interface HouseResourcesMapper extends BaseMapper<HouseResources> {}
Service层

此处实现的是spring的服务,为dubbo服务的具体实现细节,无需对外暴露,同时需要进行事务控制和其他判断逻辑

定义接口

package com.haoke.server.service;
import com.haoke.server.pojo.HouseResources;

public interface HouseResourcesService {
    /**
     *
     * @param houseResources
     * @return -1:输入的参数不符合要求,0:数据插入数据库失败,1:成功
     */
    int saveHouseResources(HouseResources houseResources);
}

编写实现类

通用CRUD实现

package com.haoke.server.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.haoke.dubbo.server.pojo.BasePojo;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.Date;
import java.util.List;

public class BaseServiceImpl<T extends BasePojo>{

    @Autowired
    private BaseMapper<T> mapper;

    /**
     * 根据id查询数据
     * @param id
     * @return
     */
    public T queryById(Long id) {
        return this.mapper.selectById(id);
    }

    /**
     * 查询所有数据
     *
     * @return
     */
    public List<T> queryAll() {
        return this.mapper.selectList(null);
    }

    /**
     * 根据条件查询一条数据
     *
     * @param record
     * @return
     */
    public T queryOne(T record) {
        return this.mapper.selectOne(new QueryWrapper<>(record));
    }

    /**
     * 根据条件查询数据列表
     * @param record
     * @return
     */
    public List<T> queryListByWhere(T record) {
        return this.mapper.selectList(new QueryWrapper<>(record));
    }

    /**
     * 根据条件分页查询数据列表
     * @param record
     * @param page
     * @param rows
     * @return
     * */
    public IPage<T> queryPageListByWhere(T record, Integer page, Integer rows) {
    // 获取分页数据
        return this.mapper.selectPage(new Page<T>(page, rows), new QueryWrapper<>
                (record));
    }

    /**
     * 保存数据
     *
     * @param record
     * @return
     */
    public Integer save(T record) {
        record.setCreated(new Date());
        record.setUpdated(record.getCreated());
        return this.mapper.insert(record);
    }
    /**
     * 更新数据
     * @param record
     * @return
     */
    public Integer update(T record) {
        record.setUpdated(new Date());
        return this.mapper.updateById(record);
    }
    /**
     * 根据id删除数据
     * @param id
     * @return
     */
    public Integer deleteById(Long id) {
        return this.mapper.deleteById(id);
    }

    /**
     * 根据ids批量删除数据
     * @param ids
     * @return
     */
    public Integer deleteByIds(List<Long> ids) {
        return this.mapper.deleteBatchIds(ids);
    }

    /**
     * 根据条件删除数据
     * @param record
     * @return
     */
    public Integer deleteByWhere(T record){
        return this.mapper.delete(new QueryWrapper<>(record));
    }
}

房源相关实现类——HouseResourcesImpl

package com.haoke.server.service.impl;

import com.alibaba.dubbo.common.utils.StringUtils;
import com.haoke.server.pojo.HouseResources;
import com.haoke.server.service.HouseResourcesService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Transactional//开启事务
@Service//这是Spring的服务
public class HouseResourcesServiceImpl
        extends BaseServiceImpl
        implements HouseResourcesService {
    @Override
    public int saveHouseResources(HouseResources houseResources) {
        // 编写校验逻辑,如果校验不通过,返回-1
        if (StringUtils.isBlank(houseResources.getTitle())) {
            return -1;
        }

        //其他校验以及逻辑省略 ……

        return super.save(houseResources);
    }
}
Dubbo服务实现

暴露新增房源的dubbo服务,将接口作为Dubbo服务导出

package com.haoke.server.api;

import com.alibaba.dubbo.config.annotation.Service;
import com.haoke.server.pojo.HouseResources;
import com.haoke.server.service.HouseResourcesService;
import org.springframework.beans.factory.annotation.Autowired;

//实现Dubbo,对外暴露服务
@Service(version = "${dubbo.service.version}")
public class ApiHoukeResourcesImpl implements ApiHouseResourcesService{

    @Autowired
    private HouseResourcesService resourcesService;

    @Override
    public int saveHouseResources(HouseResources houseResources) {
        return this.resourcesService.saveHouseResources(houseResources);
    }
}
Dubbo服务启动

启动SpringBoot程序,将Dubbo服务导出到注册中心

package com.haoke.server;

import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;

@SpringBootApplication
public class DubboProvider {

    public static void main(String[] args) {
        new SpringApplicationBuilder(DubboProvider.class)
                .web(WebApplicationType.NONE)//不是web应用
                .run(args);
    }
}
启用DubboAdmin
cd /opt/incubator-dubbo-ops/

mvn --projects dubbo-admin-server spring-boot:run
查询dubbo服务提供方

【Java项目】好客租房——前台&后台系统_第22张图片

dubbo-provider-house-resources,端口为20880

服务消费方

haoke-manage-api-server

  • 为前端系统提供RESTful风格接口
  • dubbo的消费方

在这里插入图片描述

添加依赖

因为dubbo是消费方,需要添加dubbo提供方提供的接口、pojo的依赖

<dependencies>
    
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-webartifactId>
    dependency>
    
    <dependency>
        <groupId>com.haoke.managegroupId>
        <artifactId>haoke-manage-dubbo-server-house-resources-interfaceartifactId>
        <version>1.0-SNAPSHOTversion>
    dependency>
dependencies>
消费方配置文件
# Spring boot application
spring.application.name = haoke-manage-api-server
server.port = 9091

#logging.level.root=DEBUG

# 应用名称
dubbo.application.name = dubbo-consumer-haoke-manage

# zk注册中心  服务消费方从注册中心订阅服务
dubbo.registry.address = zookeeper://8.140.130.91:2181
dubbo.registry.client = zkclient

dubbo.service.version = 1.0.0
服务消费方

HouseResourceService用于调用dubbo服务

package com.haoke.api.service;

import com.alibaba.dubbo.config.annotation.Reference;
import com.haoke.server.api.ApiHouseResourcesService;
import com.haoke.server.pojo.HouseResources;
import org.springframework.stereotype.Service;

@Service
public class HouseResourceService {

    @Reference(version = "${dubbo.service.version}")
    private ApiHouseResourcesService apiHouseResourcesService;

    public boolean save(HouseResources houseResources){
        int result = this.apiHouseResourcesService.saveHouseResources(houseResources);

        return result==1;
    }
}
控制层
package com.haoke.api.controller;

import com.haoke.api.service.HouseResourceService;
import com.haoke.server.pojo.HouseResources;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

@RequestMapping("house/resources")
@Controller
public class HouseResourcesController {

    @Autowired
    private HouseResourceService houseResourceService;

    /**
     * 新增房源
     *
     * @param houseResources json数据
     * @return
     */
    @PostMapping
    @ResponseBody
    public ResponseEntity<Void> save(@RequestBody HouseResources houseResources){
        try {
            boolean bool = this.houseResourceService.save(houseResources);
            if(bool){
                return ResponseEntity.status(HttpStatus.CREATED).build();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
    }

    /**
     * test
     * @return
     */
    @GetMapping
    @ResponseBody
    public ResponseEntity<String> get(){
        System.out.println("get House Resources");
        return ResponseEntity.ok("ok");
    }
}
测试程序
package com.haoke.api;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class DubboApiApplication {
    public static void main(String[] args) {
        SpringApplication.run(DubboApiApplication.class, args);
    }
}
测试接口

【Java项目】好客租房——前台&后台系统_第23张图片

前后端整合

增加model

新建 models 文件夹

【Java项目】好客租房——前台&后台系统_第24张图片

import { routerRedux } from 'dva/router';
import { message } from 'antd';
import { addHouseResource } from '@/services/haoke/haoke';

export default {
  namespace: 'house',

  state: {
  },

  effects: {
    *submitHouseForm({ payload }, { call }) {
      console.log("page model")
      yield call(addHouseResource, payload);
      message.success('提交成功');
    }
  },

  reducers: {

  },
};
增加services
import request from '@/utils/request';

export async function addHouseResource(params) {
  return request('/haoke/house/resources', {
    method: 'POST',
    body: params
  });
}
修改表单提交地址
handleSubmit = e => {
    const { dispatch, form } = this.props;
    e.preventDefault();
    form.validateFieldsAndScroll((err, values) => {
        if (!err) {
            if(values.facilities){
                values.facilities = values.facilities.join(",");
            }
            if(values.floor_1 && values.floor_2){
                values.floor = `${values.floor_1  }/${  values.floor_2}`;

            }

            values.houseType = `${values.houseType_1  }室${  values.houseType_2  }厅${
            values.houseType_3  }卫${  values.houseType_4  }厨${
            values.houseType_2  }阳台`;
            delete values.floor_1;
            delete values.floor_2;
            delete values.houseType_1;
            delete values.houseType_2;
            delete values.houseType_3;
            delete values.houseType_4;
            delete values.houseType_5;
            dispatch({
                type: 'house/submitHouseForm',
                payload: values,
            });
        }
    });
};
通过反向代理解决跨域问题

https://umijs.org/zh-CN/config#proxy

proxy: {
    '/haoke/': {
        target: 'http://127.0.0.1:9091',//目标地址
        changeOrigin: true,
        pathRewrite: { '^/haoke/': '' },//路径覆盖
    },
},

代理效果:

请求:http://127.0.0.1:8000/haoke/house/resources

实际:http://127.0.0.1:9091/house/resources

房源列表

在这里插入图片描述

  • PageInfo:返回给服务消费方的数据
  • ApiHouseResourcesService:暴露 Dubbo 服务提供方接口
  • ApiHaoKeResourcesImpl: Dubbo 服务提供方的实现
  • HouseResourcesService: spring 服务层定义
  • HouseResourcesServiceImpl:spring 业务的实现
  • BaseServiceImpl:Mybatisplus 层访问数据库

1. 定义dubbo服务

haoke-manage-server-house-resources-dubbo-interface

Dubbo服务提供方接口

package com.haoke.server.api;

import com.haoke.server.pojo.HouseResources;
import com.haoke.server.vo.PageInfo;

public interface ApiHouseResourcesService {

    /**
     * @param houseResources
     *
     * @return -1:输入的参数不符合要求,0:数据插入数据库失败,1:成功
     */
    int saveHouseResources(HouseResources houseResources);

    /**
     * 分页查询房源列表
     *
     * @param page 当前页
     * @param pageSize 页面大小
     * @param queryCondition 查询条件
     * @return
     */
    PageInfo<HouseResources> queryHouseResourcesList(int page, int pageSize, HouseResources queryCondition);
}

2. Dubbo生产方

haoke-manage-dubbo-server-house-resources-service

1. 定义数据模型

服务提供方封装返回的数据

package com.haoke.server.vo;

import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.Collections;
import java.util.List;

@Data
@AllArgsConstructor
public class PageInfo<T> implements java.io.Serializable{
    /**
     * 总条数
     */
    private Integer total;
    /**
     * 当前页
     */
    private Integer pageNum;
    /**
     * 一页显示的大小
     */
    private Integer pageSize;
    /**
     * 数据列表
     */
    private List<T> records = Collections.emptyList();
}
2. 服务提供方实现

dubbo服务的实现实际上为调用Spring的服务层业务

package com.haoke.server.api;

import com.alibaba.dubbo.config.annotation.Service;
import com.haoke.server.pojo.HouseResources;
import com.haoke.server.service.HouseResourcesService;
import com.haoke.server.vo.PageInfo;
import org.springframework.beans.factory.annotation.Autowired;

//实现Dubbo,对外暴露服务
@Service(version = "${dubbo.service.version}")
public class ApiHaokeResourcesImpl implements ApiHouseResourcesService{

    @Autowired
    private HouseResourcesService houseResourcesService;

    @Override
    public int saveHouseResources(HouseResources houseResources) {
        return this.houseResourcesService.saveHouseResources(houseResources);
    }

    @Override
    public PageInfo<HouseResources> queryHouseResourcesList(int page, int pageSize, HouseResources queryCondition) {
        return this.houseResourcesService.queryHouseResourcesList(page, pageSize, queryCondition);
    }
}
3. 列表业务实现
Dao层

mybatisplus从数据库获取数据

@Override
public PageInfo<HouseResources> queryHouseResourcesList(int page, int pageSize, HouseResources queryCondition) {
    QueryWrapper<HouseResources> queryWrapper = new QueryWrapper<HouseResources>(queryCondition);
    queryWrapper.orderByDesc("updated");//按更新时间降序排列

    IPage iPage = super.queryPageList(queryWrapper, page, pageSize);
    return new PageInfo<HouseResources>(Long.valueOf(iPage.getTotal()).intValue() , page, pageSize, iPage.getRecords());
}
Service层

spring的服务层实现查询列表业务

spring服务层定义

package com.haoke.server.service;

import com.haoke.server.pojo.HouseResources;
import com.haoke.server.vo.PageInfo;

public interface HouseResourcesService {
    /**
     *
     * @param houseResources
     * @return -1:输入的参数不符合要求,0:数据插入数据库失败,1:成功
     */
    int saveHouseResources(HouseResources houseResources);

    public PageInfo<HouseResources> queryHouseResourcesList(int page, int pageSize, HouseResources queryCondition);
}

spring服务层实现

package com.haoke.server.service.impl;

import com.alibaba.dubbo.common.utils.StringUtils;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.haoke.server.pojo.HouseResources;
import com.haoke.server.service.HouseResourcesService;
import com.haoke.server.vo.PageInfo;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Transactional//开启事务
@Service//这是Spring的服务
public class HouseResourcesServiceImpl
        extends BaseServiceImpl
        implements HouseResourcesService {
    @Override
    public int saveHouseResources(HouseResources houseResources) {
        // 编写校验逻辑,如果校验不通过,返回-1
        if (StringUtils.isBlank(houseResources.getTitle())) {
            return -1;
        }

        //其他校验以及逻辑省略 ……

        return super.save(houseResources);
    }

    @Override
    public PageInfo<HouseResources> queryHouseResourcesList(int page, int pageSize, HouseResources queryCondition) {
        QueryWrapper<Object> queryWrapper = new QueryWrapper<>(queryCondition);
        queryWrapper.orderByDesc("updated");
        IPage iPage = super.queryPageList(queryWrapper, page, pageSize);
        return new PageInfo<HouseResources>(Long.valueOf(iPage.getTotal()).intValue() , page, pageSize, iPage.getRecords());
    }
}

3. Dubbo消费方

实现RESTful风格接口

在这里插入图片描述

  • TableResult:返回给前端的vo
  • Pagination:分页信息
  • HouseResourceService:调用服务提供方提供的接口
  • HouseResourcesController:服务消费方提供接口给前端调用
1. 定义vo
@Data
@AllArgsConstructor
public class TableResult<T> {
    private List<T> list;
    private Pagination pagination;
}

@Data
@AllArgsConstructor
public class Pagination {
    private Integer current;
    private Integer pageSize;
    private Integer total;
}
2. 调用服务提供方
public TableResult queryList(HouseResources houseResources, Integer currentPage, Integer pageSize) {
    PageInfo<HouseResources> pageInfo
        = this.apiHouseResourcesService.queryHouseResourcesList(currentPage, pageSize, houseResources);

    return new TableResult(
        pageInfo.getRecords(),
        new Pagination(currentPage, pageSize, pageInfo.getTotal()));
}
3. 服务消费方提供给前端接口
/**
 * 查询房源列表
 * @param houseResources
 * @param currentPage
 * @param pageSize
 * @return
 */
@GetMapping("/list")//完整请求路径是/house/resource/list
@ResponseBody
public ResponseEntity<TableResult> list(HouseResources houseResources,
                                        @RequestParam(name = "currentPage", defaultValue = "1") Integer currentPage,
                                        @RequestParam(name = "pageSize",defaultValue = "10") Integer pageSize) {

    return ResponseEntity.ok(this.houseResourceService.queryList(houseResources, currentPage, pageSize));
}
4. 测试接口

【Java项目】好客租房——前台&后台系统_第25张图片

4. 前后端整合

【Java项目】好客租房——前台&后台系统_第26张图片

【Java项目】好客租房——前台&后台系统_第27张图片
【Java项目】好客租房——前台&后台系统_第28张图片

1. 修改前端表结构
columns = [
    {
        title: '房源编号',
        dataIndex: 'id',
    },
    {
        title: '房源信息',
        dataIndex: 'title',
    },
    {
        title: '图',
        dataIndex: 'pic',
        render : (text, record, index) => <ShowPics pics={text} />
    },
    {
        title: '楼栋',
        render : (text, record, index) => `${record.buildingFloorNum  }${record.buildingNum}单元${record.buildingUnit}`
    },
    {
        title: '户型',
        dataIndex: 'houseType'
    },
    {
        title: '面积',
        dataIndex: 'useArea',
        render : (text, record, index) => `${text}平方`
    },
    {
        title: '楼层',
        dataIndex: 'floor'
    },
    {
        title: '操作',
        render: (text, record) => (
            <Fragment>
            <a onClick={() => this.handleUpdateModalVisible(true, record)}>查看</a>
    <Divider type="vertical" />
    <a href="">删除</a>
    </Fragment>
    ),
},
    ];
2. 自定义图片展示组件
import React from 'react';
import { Modal, Button, Carousel } from 'antd';

class ShowPics extends React.Component{
  info = () => {
    Modal.info({
      title: '',
      iconType:'false',
      width: '800px',
      okText: "ok",
      content: (
        
{ this.props.pics.split(',').map((value,index) =>
) }
), onOk() {}, }); }; constructor(props){ super(props); this.state={ btnDisabled: !this.props.pics } } render() { return (
) } } export default ShowPics;
3. model层
import { queryResource } from '@/services/haoke/houseResource';

export default {
  namespace: 'houseResource',

  state: {
    data: {
      list: [],
      pagination: {},
    },
  },

  effects: {
    *fetch({ payload }, { call, put }) {
      console.log("houseResource fetch")

      const response = yield call(queryResource, payload);
      yield put({
        type: 'save',
        payload: response,
      });
    }
  },

  reducers: {
    save(state, action) {// state表示当前model的数据,action表示异步函数 put ,put()中的payload为封装了回调数据的属性
      return {
        ...state,
        data: action.payload,
      };
    },
  },
};
4. 修改数据请求地址
import request from '@/utils/request';
import { stringify } from 'qs';

export async function queryResource(params) {
  return request(`/haoke/house/resources/list?${stringify(params)}`);
}

GraphQL

  • 使用GraphQL开发房源接口
  • 实现房源列表查询的接口

简介

【Java项目】好客租房——前台&后台系统_第29张图片

官网地址

一种用于前后端 数据查询 方式的规范

RESTful存在的问题

GET http://127.0.0.1/user/1 #查询
POST http://127.0.0.1/user #新增
PUT http://127.0.0.1/user #更新
DELETE http://127.0.0.1/user #删除

场景一:

只需某一对象的部分属性,但通过RESTful返回的是这个对象的所有属性

#请求
GET http://127.0.0.1/user/1001
#响应:
{
    id : 1001,
    name : "张三",
    age : 20,
    address : "北京市",
    ……
}

场景二:

一个需求,要发起多次请求才能完成

#查询用户信息
GET http://127.0.0.1/user/1001
#响应:
{
    id : 1001,
    name : "张三",
    age : 20,
    address : "北京市",
    ……
} 

#查询用户的身份证信息
GET http://127.0.0.1/card/8888
#响应:
{
    id : 8888,
    name : "张三",
    cardNumber : "999999999999999",
    address : "北京市",
    ……
}

GraphQL的优势

1. 按需索取数据

当请求中只有name属性时,响应结果中只包含name属性,如果请求中添加appearsIn属性,那么结果中就会返回appearsIn的值

演示地址:https://graphql.cn/learn/schema/#type-system

2. 一次查询多个数据

【Java项目】好客租房——前台&后台系统_第30张图片

一次请求,不仅查询到了hero数据,而且还查询到了friends数据。节省了网络请求次数

3. API的演进无需划分版本

【Java项目】好客租房——前台&后台系统_第31张图片

当API进行升级时,客户端可以不进行升级,可以等到后期一起升级,这样就大大减少了客户端和服务端的耦合度

GraphQL查询的规范

GraphQL定义了一套规范,用来描述语法定义 http://graphql.cn/learn/queries/

规范 ≠ \neq = 实现

字段 Fields

在GraphQL的查询中,请求结构中包含了所预期结果的结构,这个就是字段。并且响应的结构和请求结构基本一致,这是GraphQL的一个特性,这样就可以让请求发起者很清楚的知道自己想要什么。

【Java项目】好客租房——前台&后台系统_第32张图片

参数Arguments

语法:(参数名:参数值)

【Java项目】好客租房——前台&后台系统_第33张图片

别名 Aliases

如果一次查询多个 相同对象 ,但是 值不同 ,这个时候就需要起别名了,否则json的语法就不能通过了

【Java项目】好客租房——前台&后台系统_第34张图片

片段 Fragments

查询对的属性如果相同,可以采用片段的方式进行简化定义

【Java项目】好客租房——前台&后台系统_第35张图片

GraphQL的schema和类型规范

Schema用于定义数据结构

https://graphql.cn/learn/schema/

Schema定义结构

每一个 GraphQL 服务都有一个 query 类型,可能有一个 mutation 类型。这两个类型和常规对象类型无差,但是它们之所以特殊,是因为它们定义了每一个 GraphQL 查询的入口

schema { #定义查询
	query: UserQuery
}

type UserQuery{# 定义查询的类型
	user(id:ID):User #指定对象以及参数类型
}

type User{# 定义对象
	id:ID! #!表示该属性必须不可为空
	name:String
	age:Int
}
标量类型
  • Int :有符号 32 位整数。
  • Float :有符号双精度浮点值。
  • String :UTF‐8 字符序列。
  • Boolean : true 或者 false 。
  • ID :ID 标量类型表示一个唯一标识符,通常用以重新获取对象或者作为缓存中的键

GraphQL支持自定义类型,比如在graphql-java实现中增加了:Long、Byte等。

枚举类型
enum Episode{# 定义枚举
	NEWHOPE
	EMPIRE
	JEDI
}

type huma{
	id: ID!
	name: String!
	appearsIn: [Episode]! #使用枚举类型  表示一个 Episode 数组
	homePlanet: String
}
接口 interface

一个接口是一个抽象类型,它包含某些字段,而对象类型必须包含这些字段,才能算实现了这个接口

interface Character{# 定义接口
	id: ID!
	name: String!
	friends: [Character]
	appearsIn: [Episode]!
}

#实现接口
type Human implememts Character{
	id: ID!
	name: String!
	friends: [Character]!
	starship: [Startships]!
	
	totalCredits: Int
}
type Droid implements Character {
	id: ID!
	name: String!
	friends: [Character]
	appearsIn: [Episode]!
	
	primaryFunction: String
}

GraphQL的Java实现

官方只是定义了规范并没有做实现,就需要有第三方来进行实现了

官网:https://www.graphql-java.com/

【Java项目】好客租房——前台&后台系统_第36张图片

https://www.graphql-java.com/documentation/v16/getting-started/

graphQL并未发布到maven中央仓库中,需要添加第三方仓库,才能下载到依赖

Maven:若使用mirrors配置镜像,则第三方配置不会生效

1. 导入依赖


<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">
    <modelVersion>4.0.0modelVersion>
    <groupId>org.examplegroupId>
    <artifactId>graphqlartifactId>
    <version>1.0-SNAPSHOTversion>

    <repositories>
        <repository>
            <snapshots>
                <enabled>falseenabled>
            snapshots>
            <id>bintray-andimarek-graphql-javaid>
            <name>bintrayname>
            <url>https://dl.bintray.com/andimarek/graphql-javaurl>
        repository>
    repositories>

    <dependencies>
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
        dependency>
        <dependency>
            <groupId>com.graphql-javagroupId>
            <artifactId>graphql-javaartifactId>
            <version>11.0version>
        dependency>
    dependencies>
project>

2. 安装插件

【Java项目】好客租房——前台&后台系统_第37张图片

在这里插入图片描述

schema {
    query: UserQuery
}

type UserQuery{
    user(id:ID): User
}

type User{
    id: ID!
    name: String
    age: Int
}

Java API实现

按需返回
public class GraphQLDemo {
    public static void main(String[] args) {
        /**
         * 定义User对象类型
         * type User { #定义对象
         *  id:Long! # !表示该属性是非空项
         *  name:String
         *  age:Int
         * }
         * @return
         */
        GraphQLObjectType userType = newObject()
            .name("User")
            .field(newFieldDefinition().name("id").type(GraphQLLong))
            .field(newFieldDefinition().name("name").type(GraphQLString))
            .field(newFieldDefinition().name("age").type(GraphQLInt))
            .build();

        /**
         * 定义查询的类型
         * type UserQuery { #定义查询的类型
         *  user : User #指定对象
         * }
         * @return
         */
        GraphQLObjectType userQuery = newObject()
            .name("userQuery")
            .field(newFieldDefinition()
                   .name("user")
                   .type(userType)
                   .dataFetcher(new StaticDataFetcher(new User(1L,"张三",20)))
                  )
            .build();

        /**
         * 定义Schema
         * schema { #定义查询
         *  query: UserQuery
         * }
         * @return
         */
        GraphQLSchema graphQLSchema = GraphQLSchema.newSchema()
            .query(userQuery)
            .build();

        //构建GraphQL查询器
        GraphQL graphQL = GraphQL.newGraphQL(graphQLSchema).build();

        //查询结果
        String query = "{user{id,name}}";
        ExecutionResult executionResult = graphQL.execute(query);

        // 打印错误
        System.out.println("错误:" + executionResult.getErrors());
        // 打印数据
        System.out.println("结果:" +(Object) executionResult.toSpecification());
    }
}
查询参数的设置

【Java项目】好客租房——前台&后台系统_第38张图片

public class GraphQLDemo {
    public static void main(String[] args) {
        /**
         * 定义User对象类型
         * type User { #定义对象
         *  id:Long! # !表示该属性是非空项
         *  name:String
         *  age:Int
         * }
         * @return
         */
        GraphQLObjectType userType = newObject()
            .name("User")
            .field(newFieldDefinition().name("id").type(GraphQLLong))
            .field(newFieldDefinition().name("name").type(GraphQLString))
            .field(newFieldDefinition().name("age").type(GraphQLInt))
            .build();

        /**
         * 定义查询的类型
         * type UserQuery { #定义查询的类型
         *  user : User #指定对象
         * }
         * @return
         */
        GraphQLObjectType userQuery = newObject()
            .name("userQuery")
            .field(newFieldDefinition()
                   .name("user")
                   .argument(GraphQLArgument.newArgument()
                            .name("id")
                             .type("GraphQLLong")
                            )
                   .type(userType)
                   .dataFetcher(
                       Environment->{
                            Long id = Environment.getArgument("id");
                            //查询数据库
                            //TODO
                            return new User(id,"张三",id.intValue()+10);
                        })
                  )
            .build();

        /**
         * 定义Schema
         * schema { #定义查询
         *  query: UserQuery
         * }
         * @return
         */
        GraphQLSchema graphQLSchema = GraphQLSchema.newSchema()
            .query(userQuery)
            .build();

        //构建GraphQL查询器
        GraphQL graphQL = GraphQL.newGraphQL(graphQLSchema).build();

        //查询结果
        String query = "{user(id:100){id,name,age}}";
        ExecutionResult executionResult = graphQL.execute(query);

        // 打印错误
        System.out.println("错误:" + executionResult.getErrors());
        // 打印数据
        System.out.println("结果:" +(Object) executionResult.toSpecification());
    }
}

3. SDL构建Schema

SDL通过插件将GraphQL定义文件转换为java

schema {
    query: UserQuery
}

type UserQuery{
    user(id:ID): User
}

type User{
    id: ID!
    name: String
    age: Int
    card: Card
}

type Card {
    cardNumber:String!
    userId: ID
}
public class GraphQLSDLDemo {
    public static void main(String[] args) throws IOException {
        /* 1. 读取资源,进行解析 */
        //资源名
        String fileName = "user.graphql";
        /*
        
            org.apache.commons
            commons-lang3
        
        * */
        String fileContent = IOUtils.toString(GraphQLSDLDemo.class.getClassLoader().getResource(fileName),"UTF-8");
        TypeDefinitionRegistry tyRegistry = new SchemaParser().parse(fileContent);

        /* 2. 数据查询 */
        RuntimeWiring wiring = RuntimeWiring.newRuntimeWiring()
                .type("UserQuery",builder ->
                        builder.dataFetcher("user", Environment->{
                            Long id = Long.parseLong(Environment.getArgument("id"));
                            Card card = new Card("number_"+id,id);

                            return new User(id,"张三_"+id,id.intValue()+10,card);
                        })
                )
                .build();

        /* 3. 生成schema */
        GraphQLSchema graphQLSchema = new SchemaGenerator().makeExecutableSchema(tyRegistry,wiring);

        /* 4. 根据schema对象生成GraphQL对象 */
        GraphQL graphQL = GraphQL.newGraphQL(graphQLSchema).build();

        String query = "{user(id:100){id,name,age,card{cardNumber}}}";
        ExecutionResult executionResult = graphQL.execute(query);

        System.out.println(executionResult.toSpecification());
    }
}

在这里插入图片描述

id查询房源接口

在这里插入图片描述

dubbo服务提供方

HouseResourcesService——Spring服务的Interface
public HouseResources queryHouseResourcesById(Long id);
HouseResourcesServiceImpl——Spring服务的实现
@Override
public HouseResources queryHouseResourcesById(Long id) {
    return (HouseResources) super.queryById(id);
}
ApiHouseResourcesService——dubbo服务提供方接口
/*
    * 实现通过id查询 房源
    *
    * @Param id 房源id
    * @return
    * */
HouseResources queryHouseResourcesById(Long id);
ApiHaokeResourcesImpl——dubbo服务提供方实现
@Override
public HouseResources queryHouseResourcesById(Long id) {
    return houseResourcesService.queryHouseResourcesById(id);
}

dubbo服务消费方

HouseResourceService

@Reference(version = "${dubbo.service.version}")
private ApiHouseResourcesService apiHouseResourcesService;

/*
    * 根据id查询房源数据
    *
    * @Param id
    * @Return
    * */
public HouseResources queryHouseResourcesById(Long id){
    //调用dubbo服务查询数据

    return this.apiHouseResourcesService.queryHouseResourcesById(id);

GraphQL接口

导入依赖
<repositories>
    <repository>
        <snapshots>
            <enabled>falseenabled>
        snapshots>
        <id>bintray-andimarek-graphql-javaid>
        <name>bintrayname>
        <url>https://dl.bintray.com/andimarek/graphql-javaurl>
    repository>
repositories>

<dependency>
    <groupId>com.graphql-javagroupId>
    <artifactId>graphql-javaartifactId>
    <version>16.0version>
dependency>
GraphQL定义

haoke.graphql

schema {
    query: HaokeQuery
}

type HaokeQuery{       
	# 通过Id查询房源信息
    HouseResources(id:ID): HouseResources
}

type HouseResources{
    id:ID!
    title:String
    estateId:ID
    buildingNum:String
    buildingUnit:String
    buildingFloorNum:String
    rent:Int
    rentMethod:Int
    paymentMethod:Int
    houseType:String
    coveredArea:String
    useArea:String
    floor:String
    orientation:String
    decoration:Int
    facilities:String
    pic:String
    houseDesc:String
    contact:String
    mobile:String
    time:Int
    propertyCost:String
}
GraphQL组件

graphql —— Bean

@Component//将GraphQL对象注入IoC容器,并完成GraphQL的初始化
public class GraphQLProvider {
    private GraphQL graphQL;

    @Autowired
    private HouseResourceService houseResourceService;

    @PostConstruct//在IoC容器初始化时运行
    public void init() throws FileNotFoundException {
        //导入graphql脚本
        File file = ResourceUtils.getFile("classpath:haoke.graphql");

        //初始化graphql
        this.graphQL = GraphQL.newGraphQL(//schema { query: HaokeQuery}
                new SchemaGenerator().makeExecutableSchema(
                        new SchemaParser().parse(file),//TypeDefinitionRegistry
                        RuntimeWiring.newRuntimeWiring()//RuntimeWiring
                                .type("HaokeQuery",builder ->
                                        builder.dataFetcher("HouseResources", Environment->{
                                            Long id = Long.parseLong(Environment.getArgument("id"));

                                            return this.houseResourceService.queryHouseResourcesById(id);
                                        })
                                        )
                                .build()
                )
        ).build();
    }

    @Bean
    GraphQL graphQL(){
        return this.graphQL;
    }
}
暴露接口
@RequestMapping("graphql")
@Controller
public class GraphQLController {

    @Autowired
    private GraphQL graphQL;

    @GetMapping
    @ResponseBody
    public Map<String,Object> graphql(@RequestParam("query")String query){
        return this.graphQL.execute(query).toSpecification();
    }
}

测试

【Java项目】好客租房——前台&后台系统_第39张图片

GraphQL组件获取的优化

问题

【Java项目】好客租房——前台&后台系统_第40张图片

每当增加查询时,都需要修改该方法

改进思路

  1. 编写接口
  2. 所有实现查询的逻辑都实现该接口
  3. 在GraphQLProvider中使用该接口的实现类进行处理
  4. 以后新增查询逻辑只需增加实现类即可

1. 编写MyDataFetcher接口

package com.haoke.api.graphql;

import graphql.schema.DataFetchingEnvironment;

public interface MyDataFetcher {

    /**
     * 查询名称
     *
     * @return
     */
    String fieldName();

    /**
     * 具体实现数据查询的逻辑
     *
     * @param environment
     * @return
     */
    Object dataFetcher(DataFetchingEnvironment environment);
}

2. 实现MyDataFetcher

@Component
public class HouseResourcesDataFetcher implements MyDataFetcher {
    @Autowired
    HouseResourceService houseResourceService;

    @Override
    public String fieldName() {
        return "HouseResources";
    }

    @Override
    public Object dataFetcher(DataFetchingEnvironment environment) {
        Long id = Long.parseLong(environment.getArgument("id"));

        return this.houseResourceService.queryHouseResourcesById(id);
    }
}

3. 修改GraphQLProvider

this.graphQL = GraphQL.newGraphQL(
    new SchemaGenerator().makeExecutableSchema(
        new SchemaParser().parse(file),//TypeDefinitionRegistry
        RuntimeWiring.newRuntimeWiring()//RuntimeWiring
        .type("HaokeQuery",builder ->{
            for (MyDataFetcher myDataFetcher : myDataFetchers) {
                builder.dataFetcher(
                    myDataFetcher.fieldName(),
                    Environment->myDataFetcher.dataFetcher(Environment)
                 );
            }
            return builder;
        }
     )
     .build()
)

房源接口(GraphQL)

首页轮播广告

1. 数据结构

请求地址:

【Java项目】好客租房——前台&后台系统_第41张图片

响应:

【Java项目】好客租房——前台&后台系统_第42张图片

所以,数据只需要返回图片链接即可

2. 数据表设计

use haoke;

CREATE TABLE `tb_ad` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`type` int(10) DEFAULT NULL COMMENT '广告类型',
`title` varchar(100) DEFAULT NULL COMMENT '描述',
`url` varchar(200) DEFAULT NULL COMMENT '图片URL地址',
`created` datetime DEFAULT NULL,
`updated` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='广告表';

INSERT INTO `tb_ad` (`id`, `type`, `title`, `url`, `created`, `updated`) VALUES (
'1','1', 'UniCity万科天空之城', 
'https://haoke-1257323542.cos.ap-beijing.myqcloud.com/ad-swipes/1.jpg', 
'2021-3-24 16:36:11','2021-3-24 16:36:16');
INSERT INTO `tb_ad` (`id`, `type`, `title`, `url`, `created`, `updated`) VALUES (
'2','1', '天和尚海庭前',
'https://haoke-1257323542.cos.ap-beijing.myqcloud.com/ad-swipes/2.jpg', 
'2021-3-24 16:36:43','2021-3-24 16:36:37');
INSERT INTO `tb_ad` (`id`, `type`, `title`, `url`, `created`, `updated`) VALUES (
'3', '1', '[奉贤 南桥] 光语著', 
'https://haoke-1257323542.cos.ap-beijing.myqcloud.com/ad-swipes/3.jpg', 
'2021-3-24 16:38:32','2021-3-24 16:38:26');
INSERT INTO `tb_ad` (`id`, `type`, `title`, `url`, `created`, `updated`) VALUES (
'4','1', '[上海周边 嘉兴] 融创海逸长洲', 
'https://haoke-1257323542.cos.ap-beijing.myqcloud.com/ad-swipes/4.jpg', 
'2021-3-24 16:39:10','2021-3-24 16:39:13');

3. 实现查询接口

dubbo服务提供方

1. 创建工程

【Java项目】好客租房——前台&后台系统_第43张图片


<dependencies>
    <dependency>
        <groupId>com.haoke.managegroupId>
        <artifactId>haoke-manage-dubbo-server-commonartifactId>
        <version>1.0-SNAPSHOTversion>
    dependency>
dependencies>

<dependencies>
    <dependency>
        <groupId>com.haoke.managegroupId>
        <artifactId>haoke-manage-dubbo-server-ad-interfaceartifactId>
        <version>1.0-SNAPSHOTversion>
    dependency>
dependencies>
2.appplication.properties
# Spring boot application
spring.application.name = haoke-manage-dubbo-server-ad

# 数据库
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://8.140.130.91:3306/myhome\
  ?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&autoReconnect=true&allowMultiQueries=true
spring.datasource.username=root
spring.datasource.password=root

# hikari设置
spring.datasource.hikari.maximum-pool-size=60
spring.datasource.hikari.idle-timeout=60000
spring.datasource.hikari.connection-timeout=60000
spring.datasource.hikari.validation-timeout=3000
spring.datasource.hikari.login-timeout=5
spring.datasource.hikari.max-lifetime=60000

# 服务的扫描包
dubbo.scan.basePackages = com.haoke.server.api

# 应用名称
dubbo.application.name = dubbo-provider-ad
dubbo.service.version = 1.0.0

# 协议以及端口
dubbo.protocol.name = dubbo
dubbo.protocol.port = 21880
# zk注册中心
dubbo.registry.address = zookeeper://8.140.130.91:2181
dubbo.registry.client = zkclient
3.Dao层
POJO

【Java项目】好客租房——前台&后台系统_第44张图片

@Data
@TableName("tb_ad")
public class Ad extends BasePojo{
    private static final long serialVersionUID = -493439243433085768L;
    
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;

    //广告类型
    private Integer type;
    //描述
    private String title;
    //'图片URL地址
    private String url;
}
package com.haoke.server.api;

import com.haoke.server.pojo.Ad;
import com.haoke.server.vo.PageInfo;

public interface ApiAdService {
    /**
     * 分页查询广告数据
     *
     * @param type 广告类型
     * @param page 页数
     * @param pageSize 每页显示的数据条数
     * @return
     */
    PageInfo<Ad> queryAdList(Integer type, Integer page, Integer pageSize);
}
AdMapper
package com.haoke.server.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.haoke.server.pojo.Ad;

public interface AdMapper extends BaseMapper<Ad> {}
MybatisPlusConfig

分页配置

package com.haoke.server.config;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@MapperScan("com.haoke.server.mapper")
@Configuration
public class MybatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();

        PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
        paginationInnerInterceptor.setDbType(DbType.MYSQL);

        interceptor.addInnerInterceptor(paginationInnerInterceptor);

        return interceptor;
    }
}
4.Service层

实现业务

【Java项目】好客租房——前台&后台系统_第45张图片

编写接口:

package com.haoke.server.service;

import com.haoke.server.pojo.Ad;
import com.haoke.server.vo.PageInfo;

public interface AdService {
    PageInfo<Ad> queryAdList(Ad ad, Integer page, Integer pageSize);
}

实现接口:

package com.haoke.server.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.haoke.server.pojo.Ad;
import com.haoke.server.service.AdService;
import com.haoke.server.service.BaseServiceImpl;
import com.haoke.server.vo.PageInfo;
import org.springframework.stereotype.Service;

@Service
public class AdServiceImpl extends BaseServiceImpl implements AdService {
    @Override
    public PageInfo<Ad> queryAdList(Ad ad, Integer page, Integer pageSize) {
        QueryWrapper queryWrapper = new QueryWrapper();

        //排序
        queryWrapper.orderByDesc("updated");
        //按广告的类型查询
        queryWrapper.eq("type",ad.getType());

        IPage iPage = super.queryPageList(queryWrapper,page,pageSize);

        return new PageInfo<>(Long.valueOf(iPage.getTotal()).intValue(),page,pageSize,iPage.getRecords());
    }
}
5.dubbo服务实现类
package com.haoke.server.api;

import com.haoke.server.pojo.Ad;
import com.haoke.server.vo.PageInfo;

public interface ApiAdService {
    /**
     * 分页查询广告数据
     *
     * @param type 广告类型
     * @param page 页数
     * @param pageSize 每页显示的数据条数
     * @return
     */
    PageInfo<Ad> queryAdList(Integer type, Integer page, Integer pageSize);
}
package com.haoke.server.api;

import com.alibaba.dubbo.config.annotation.Service;
import com.haoke.server.pojo.Ad;
import com.haoke.server.service.AdService;
import com.haoke.server.vo.PageInfo;
import org.springframework.beans.factory.annotation.Autowired;

@Service(version = "${dubbo.service.version}")
public class ApiAdServiceImpl implements ApiAdService{
    @Autowired
    private AdService adService;

    @Override
    public PageInfo<Ad> queryAdList(Integer type, Integer page, Integer pageSize) {
        Ad ad = new Ad();
        ad.setType(type);

        return this.adService.queryAdList(ad,page,pageSize);
    }
}
6.编写启动类
package com.haoke.server;

import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;

@SpringBootApplication
public class AdDubboProvider {

    public static void main(String[] args) {
        new SpringApplicationBuilder(AdDubboProvider.class)
                .web(WebApplicationType.NONE)//非web应用
                .run(args);
    }
}

4.API实现(Dubbo消费方)

1. 导入依赖

<dependency>
    <groupId>com.haoke.managegroupId>
    <artifactId>haoke-manage-dubbo-server-ad-interfaceartifactId>
    <version>1.0-SNAPSHOTversion>
dependency>
2. 编写WebResult

【Java项目】好客租房——前台&后台系统_第46张图片

package com.haoke.api.vo;

import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Data;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Data
@AllArgsConstructor
public class WebResult {

    @JsonIgnore
    private int status;
    @JsonIgnore
    private String msg;
    @JsonIgnore
    private List<?> list;

    @JsonIgnore
    public static WebResult ok(List<?> list) {
        return new WebResult(200, "成功", list);
    }

    @JsonIgnore
    public static WebResult ok(List<?> list, String msg) {
        return new WebResult(200, msg, list);
    }

    public Map<String, Object> getData() {
        HashMap<String, Object> data = new HashMap<String, Object>();
        data.put("list", this.list);
        return data;
    }

    public Map<String, Object> getMeta() {
        HashMap<String, Object> meta = new HashMap<String, Object>();
        meta.put("msg", this.msg);
        meta.put("status", this.status);
        return meta;
    }
}
3.编写Service
package com.haoke.api.service;

import com.alibaba.dubbo.config.annotation.Reference;
import com.haoke.api.vo.WebResult;
import com.haoke.server.api.ApiAdService;
import com.haoke.server.pojo.Ad;
import com.haoke.server.vo.PageInfo;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class AdService {

    @Reference(version = "1.0.0")
    private ApiAdService apiAdService;

    public PageInfo<Ad> queryAdList(Integer type, Integer page, Integer pageSize) {

        return this.apiAdService.queryAdList(type, page, pageSize);
    }
}
4.Controller
package com.haoke.api.controller;

import com.haoke.api.service.AdService;
import com.haoke.api.vo.WebResult;
import com.haoke.server.pojo.Ad;
import com.haoke.server.vo.PageInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RequestMapping("ad")
@RestController
@CrossOrigin//允许跨域
public class AdController {
    @Autowired
    private AdService adService;

    /**
     * 首页广告位
     * @return
     */
    @GetMapping
    public WebResult queryIndexad(){
        PageInfo<Ad> pageInfo = this.adService.queryAdList(1,1,3);

        List<Ad> ads = pageInfo.getRecords();
        List<Map<String,Object>> data = new ArrayList<>();
        for (Ad ad : ads) {
            Map<String,Object> map = new HashMap<>();

            map.put("original",ad.getUrl());
            data.add(map);
        }

        return WebResult.ok(data);
    }
}
测试

【Java项目】好客租房——前台&后台系统_第47张图片

5. 整合前端系统

修改home.js文件中请求地址

let swipe = new Promise((resolve, reject) => {
    axios.get('http://127.0.0.1:9091/ad').then((data)=>{
        resolve(data.data.list);
    });
})

【Java项目】好客租房——前台&后台系统_第48张图片

跨域问题:

【Java项目】好客租房——前台&后台系统_第49张图片

【Java项目】好客租房——前台&后台系统_第50张图片

6. 广告的GraphQL接口

1. 目标数据结构
{
    "list": [
        {
            "original": "http://itcast-haoke.oss-cnqingdao.aliyuncs.com/images/2018/11/26/15432030275359146.jpg"
        },
        {
            "original": "http://itcast-haoke.oss-cnqingdao.aliyuncs.com/images/2018/11/26/15432029946721854.jpg"
        },
        {
            "original": "http://itcast-haoke.oss-cnqingdao.aliyuncs.com/images/2018/11/26/1543202958579877.jpg"
        }
    ]
}
2. graphql定义语句
type HaokeQuery{
    #分页查询房源信息-应用于前台房源信息
    HouseResourcesList(page:Int, pageSize:Int):TableResult
    # 通过Id查询房源信息
    HouseResources(id:ID): HouseResources

    #首页广告图-应用于前台首页
    IndexAdList: IndexAdResult
}

type IndexAdResult{
    list:[IndexAdResultData]
}

type IndexAdResultData{
    original: String
}
3. 根据GraphQL结构编写VO
package com.haoke.api.vo.ad.index;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class IndexAdResult {
    private List<IndexAdResultData> list;
}
package com.haoke.api.vo.ad.index;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class IndexAdResultData {
    private String original;
}
4. IndexAdDataFetcher
package com.haoke.api.graphql.myDataFetcherImpl;

import com.haoke.api.graphql.MyDataFetcher;
import com.haoke.api.service.AdService;
import com.haoke.api.vo.WebResult;
import com.haoke.api.vo.ad.index.IndexAdResult;
import com.haoke.api.vo.ad.index.IndexAdResultData;
import com.haoke.server.pojo.Ad;
import com.haoke.server.vo.PageInfo;
import graphql.schema.DataFetchingEnvironment;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

@Component
public class IndexAdDataFetcher implements MyDataFetcher {

    @Autowired
    private AdService adService;

    @Override
    public String fieldName() {
        return "IndexAdList";
    }

    @Override
    public Object dataFetcher(DataFetchingEnvironment environment) {
        PageInfo<Ad> pageInfo = this.adService.queryAdList(1, 1, 3);

        List<Ad> ads = pageInfo.getRecords();

        List<IndexAdResultData> list = new ArrayList<>();
        for (Ad ad : ads) {
            list.add(new IndexAdResultData(ad.getUrl()));
        }

        return new IndexAdResult(list);
    }
}
5. 测试
{
 	IndexAdList{
    list{
      original
    }
  }
}

【Java项目】好客租房——前台&后台系统_第51张图片

7. GraphQL客户端

【Java项目】好客租房——前台&后台系统_第52张图片

参考文档:https://www.apollographql.com/docs/react/get-started/

1. 安装依赖
npm install @apollo/client graphql

【Java项目】好客租房——前台&后台系统_第53张图片

2. 创建客户端
import { ApolloClient, gql } from '@apollo/client';

const client = new ApolloClient({
  uri: 'http://127.0.0.1:9091/graphql',
});
3. 定义查询
//定义查询
const GET_INDEX_ADS = gql`
{
IndexAdList{
list{
original
}
}
}
`;

let swipe = new Promise((resolve, reject) => {
    client.query({query: GET_INDEX_ADS}).then(result =>
                                              resolve(result.data.IndexAdList.list));
})
4. 测试

【Java项目】好客租房——前台&后台系统_第54张图片

两个问题:

  1. GraphQL服务没有支持cross,Controller上标注@CrossOrigin
  2. Apollo Client发起的数据请求为POST请求,现在实现的GraphQL仅仅实现了GET请求处理
package com.haoke.api.controller;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import graphql.GraphQL;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@RequestMapping("graphql")
@Controller
@CrossOrigin//添加跨域
public class GraphQLController {

    @Autowired
    private GraphQL graphQL;

    private static final ObjectMapper MAPPER = new ObjectMapper();

    @GetMapping
    @ResponseBody
    public Map<String,Object> graphql(@RequestParam("query")String query){
        return this.graphQL.execute(query).toSpecification();
    }

    @PostMapping
    @ResponseBody
    public Map<String, Object> postGraphql(@RequestBody String json) throws IOException {

        try {
            JsonNode jsonNode = MAPPER.readTree(json);
            if(jsonNode.has("query")){
                String query = jsonNode.get("query").asText();
                return this.graphQL.execute(query).toSpecification();
            }
        }catch (IOException e){
            e.printStackTrace();
        }

    Map<String,Object> error = new HashMap<>();
        error.put("status",500);
        error.put("msg","查询出错");
        return error;
    }
}

【Java项目】好客租房——前台&后台系统_第55张图片

房源信息列表

1. 查询语句定义

haoke.graphql

schema {
    query: HaokeQuery
}

type HaokeQuery{
    #分页查询房源信息-应用于前台房源信息
    HouseResourcesList(page:Int, pageSize:Int):TableResult
    # 通过Id查询房源信息
    HouseResources(id:ID): HouseResources

    #首页广告图-应用于前台首页
    IndexAdList: IndexAdResult
}

type HouseResources{
    id:ID!
    title:String
    estateId:ID
    buildingNum:String
    buildingUnit:String
    buildingFloorNum:String
    rent:Int
    rentMethod:Int
    paymentMethod:Int
    houseType:String
    coveredArea:String
    useArea:String
    floor:String
    orientation:String
    decoration:Int
    facilities:String
    pic:String
    houseDesc:String
    contact:String
    mobile:String
    time:Int
    propertyCost:String
}

type TableResult{
    list: [HouseResources]
    pagination: Pagination
}

type Pagination{
    current:Int
    pageSize:Int
    total:Int
}

2.DataFetcher

HouseResourcesListDataFetcher

@Component
public class HouseResourcesListDataFetcher implements MyDataFetcher {

    @Autowired
    HouseResourceService houseResourceService;

    @Override
    public String fieldName() {
        return "HouseResourcesList";
    }

    @Override
    public Object dataFetcher(DataFetchingEnvironment environment) {
        Integer page = environment.getArgument("page");
        if(page == null){
            page = 1;
        }

        Integer pageSize = environment.getArgument("pageSize");
        if(pageSize == null){
            pageSize = 5;
        }
        return this.houseResourceService.queryList(null, page, pageSize);
    }
}

【Java项目】好客租房——前台&后台系统_第56张图片

3.GraphQL参数

问题分析:上述 首页轮播广告查询接口 中的参数是固定的

【Java项目】好客租房——前台&后台系统_第57张图片

实际应用中要实现根据前端的请求参数设置参数查询

https://graphql.cn/learn/queries/#variables

一种办法使直接将参数动态的设置到请求体(POST)或URL(GET)中,缺点就是可以直接通过修改查询字符串来自行获取数据。

GraphQL 拥有一级方法将动态值提取到查询之外,然后作为分离的字典传进去。这些动态值即称为变量

【Java项目】好客租房——前台&后台系统_第58张图片

前台系统发送的参数分析

query hk($id:ID){
 	HouseResources(id:$id){
    id
    title
  }
}

【Java项目】好客租房——前台&后台系统_第59张图片

【Java项目】好客租房——前台&后台系统_第60张图片

GraphQL发送的数据如上,后端需处理请求并返回相应的数据

4. 后端处理参数

【Java项目】好客租房——前台&后台系统_第61张图片

由GraphQL的调用流程可知,传入到后端的GraphQL字符串最终会被构造成一个 ExecutionInput 对象

【Java项目】好客租房——前台&后台系统_第62张图片

GraphQLController

package com.haoke.api.controller;

@RequestMapping("graphql")
@Controller
@CrossOrigin//添加跨域
public class GraphQLController {

    @Autowired
    private GraphQL graphQL;

    private static final ObjectMapper MAPPER = new ObjectMapper();

    @GetMapping
    @ResponseBody
    public Map<String,Object> graphql(@RequestParam("query")String query,
                                      @RequestParam(value = "variables",required = false) String variablesJSON,
                                      @RequestParam(value = "operationName",required = false) String operationName){

        try {
            //反序列化,将JSON字符串转化为Map对象
            Map<String, Object> variables = MAPPER.readValue(variablesJSON, MAPPER.getTypeFactory().constructMapType(HashMap.class,String.class,Object.class));

            return this.executeGraphQLQuery(query,operationName,variables);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }

        Map<String,Object> error = new HashMap<>();
        error.put("status",500);
        error.put("msg","查询出错");
        return error;
    }

    @PostMapping
    @ResponseBody
    public Map<String, Object> postGraphql(@RequestBody Map<String,Object> map) throws IOException {

        try{
            String query = (String) map.get("query");
            if(null == query){
                query = "";
            }
            String operationName = (String) map.get("operationName");
            if(null == operationName){
                operationName = "";
            }
            Map variables = (Map) map.get("variables");
            if(variables == null){
                variables = Collections.EMPTY_MAP;
            }

            return this.executeGraphQLQuery(query,operationName,variables);
        } catch (Exception e) {
            e.printStackTrace();
        }

        Map<String,Object> error = new HashMap<>();
        error.put("status",500);
        error.put("msg","查询出错");
        return error;
    }

    private Map<String, Object> executeGraphQLQuery(String query,String operationName,Map<String,Object> variables) {

        return this.graphQL.execute(
                ExecutionInput.newExecutionInput()
                        .query(query)
                        .variables(variables)
                        .operationName(operationName)
                        .build()
        ).toSpecification();
    }
}

5. 查询字符串

query HouseResourcesList($pageSize: Int, $page: Int) {
  HouseResourcesList(pageSize: $pageSize, page: $page) {
    list {
      id
      title
      pic
      title
      coveredArea
      orientation
      floor
      rent
    }
  }
}

{
	"pageSize":2,
	"page":1
}

【Java项目】好客租房——前台&后台系统_第63张图片

6. 改造list.js页面

import React from 'react';
import { withRouter } from 'react-router';
import { Icon,Item } from 'semantic-ui-react';
import config from '../../common.js';
import { ApolloClient, gql , InMemoryCache} from '@apollo/client';

const client = new ApolloClient({
  uri: 'http://127.0.0.1:9091/graphql',
  cache: new InMemoryCache()
});

//定义查询
const QUERY_LIST = gql`
  query HouseResourcesList($pageSize: Int, $page: Int) {
    HouseResourcesList(pageSize: $pageSize, page: $page) {
      list {
        id
        title
        pic
        title
        coveredArea
        orientation
        floor
        rent
      }
    }
  }
`;

class HouseList extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      listData: [],
      typeName: '',
      type: null,
      loadFlag: false
    };
  }

  goBack = () => {
    console.log(this.props.history)
    this.props.history.goBack();
  }
  componentDidMount = () => {
    const {query} = this.props.location.state;
    this.setState({
      typeName: query.name,
      type: query.type
    })
    /*axios.post('/homes/list',{
      home_type: query.type
    }).then(ret=>{
      this.setState({
        listData: ret.data,
        loadFlag: true
      })
    })*/
    client.query({query:QUERY_LIST,variables:{"pageSize":2,"page":1}}).then(result=>{
      console.log(result)
      this.setState({
        listData: result.data.HouseResourcesList.list,
        loadFlag: true
      })
    })
  }
  render() {
    let list = null;
    if(this.state.loadFlag) {
      list = this.state.listData.map(item=>{
        return (
            
              
              
                {item.title}
                
                  {item.coveredArea} ㎡/{item.orientation}/{item.floor}
                
                
                  上海
                
                {item.rent}
              
            
        )
      });
    }
    return ( 
      
{this.state.typeName}
{list}
); } } export default withRouter(HouseList);

【Java项目】好客租房——前台&后台系统_第64张图片

更新房源数据

在这里插入图片描述

1. Controller

haoke-manage-api-server

/**
 * 修改房源
 *
 * @param houseResources json数据
 * @return
 */
@PutMapping
@ResponseBody
public ResponseEntity<Void> update(@RequestBody HouseResources houseResources) {
    try {
        boolean bool = this.houseResourceService.update(houseResources);
        if (bool) {
            return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}

2. Service

haoke-manage-api-server

public boolean update(HouseResources houseResources) {
    return this.apiHouseResourcesService.updateHouseResources(houseResources);
}

3. 修改dubbo服务

haoke-manage-dubbo-server-house-resources-interface

ApiHouserResourcesService

/**
 * 修改房源
 *
 * @param houseResources
 * @return
 */
boolean updateHouseResources(HouseResources houseResources);

实现类ApiHouseResourcesServiceImpl

/**
 * 修改房源
 *
 * @param houseResources
 * @return
 */
@Override
public boolean updateHouseResources(HouseResources houseResources) {
    return this.houseResourcesService.updateHouseResources(houseResources);
}

修改业务Service:HouseResourcesServiceImpl

@Override
public boolean updateHouseResources(HouseResources houseResources) {
    return super.update(houseResources)==1;
}

BaseServiceImpl

/**
  * 更新数据
  * @param record
  * @return
  */
public Integer update(T record) {
    record.setUpdated(new Date());
    return this.mapper.updateById(record);
}

编写后台页面

1. 修改房源列表页
render: (text, record) => (
    
         this.handleUpdateModalVisible(true, record)}>查看
        
        {/* 弹窗组件 */}
        
        
        删除
    
),
    
  reload(){// 刷新当前页面
    const { dispatch } = this.props;
    dispatch({
      type: 'houseResource/fetch'
    });
  }

【Java项目】好客租房——前台&后台系统_第65张图片

2. EditResource.js
import React from 'react';
import {Card, Checkbox, Form, Input, Modal, Select} from "antd";
import {connect} from "dva";
import PicturesWall from "../Utils/PicturesWall";

const FormItem = Form.Item;
const InputGroup = Input.Group;
const CheckboxGroup = Checkbox.Group;
const { TextArea } = Input;

const formItemLayout = {
  labelCol: {
    xs: { span: 24 },
    sm: { span: 7 },
  },
  wrapperCol: {
    xs: { span: 24 },
    sm: { span: 12 },
    md: { span: 10 },
  },
};

const paymentMethod = [
  "",
  "付一押一",
  "付三押一",
  "付六押一",
  "年付押一",
  "其他"
]

const decoration = [
  "",
  "精装",
  "简装",
  "毛坯"
]

const rentMethod = [
  "",
  "整租",
  "合租"
]

const time = [
  "",
  "上午",
  "中午",
  "下午",
  "晚上",
  "全天"
]

const facilities = [
  "",
  "水",
  "电",
  "煤气/天然气",
  "暖气",
  "有线电视",
  "宽带",
  "电梯",
  "车位/车库",
  "地下室/储藏室"
]

function isChinese(temp){
  const re=/^[\u3220-\uFA29]+$/;
  if (re.test(temp))
    return true ;
  return false;
}

@connect()
@Form.create()/* 只有标注了 @Form.create() Form中的元素才可被封装 */
class EditResource extends React.Component{

  constructor(props){
    super(props);
    console.log("====传来的信息=====")
    console.log(this.props.record)
    this.state={
      visible:false,
      pics:new Set()
    };
  }

  /* 显示编辑弹窗 */
  showModal = () => {
    this.setState({
      visible: true
    });
  };

  /* 隐藏编辑弹窗 */
  handleCancel = () => {
    this.setState({
      visible: false,
    });
  };

  handleSave = () => {

    const { dispatch, form, record } = this.props;
    form.validateFieldsAndScroll((err, values) => {

      if (!err) {
        // 房源id
        values.id = record.id;

        // 看房时间
        if(isChinese(values.time)){
          for (let i = 1; i < time.length; i++) {
            if(time[i]==values.time)
              values.time=i;
          }
        }

        // 支付方式
        if(isChinese(values.paymentMethod)){
          for (let i = 1; i < paymentMethod.length; i++) {
            if(paymentMethod[i]==values.paymentMethod)
              values.paymentMethod=i;
          }
        }

        // rentMethod
        if(isChinese(values.rentMethod)){
          for (let i = 1; i < rentMethod.length; i++) {
            if(rentMethod[i]==values.rentMethod)
              values.rentMethod=i;
          }
        }

        // decoration
        if(isChinese(values.decoration)){
          for (let i = 1; i < decoration.length; i++) {
            if(decoration[i]==values.decoration)
              values.decoration=i;
          }
        }

        if(values.floor_1 && values.floor_2){
          values.floor = `${values.floor_1  }/${  values.floor_2}`;
        }

        // 周边设施
        if(values.facilities){
          values.facilities = values.facilities.join(",");
        }

        // 楼栋信息
        values.buildingNum = record.buildingNum;
        values.buildingUnit = record.buildingUnit;
        values.buildingFloorNum = record.buildingFloorNum;
        delete values.building;

        // 照片
        if(this.state.pics.size > 0){
          values.pic = [...this.state.pics].join(',');
        }else{
          values.pic = record.pic;
        }


        console.log("====提交的信息=====")
        console.log(values)
        dispatch({
          type: 'house/updateHouseForm',
          payload: values,
        });

        setTimeout(()=>{
          this.handleCancel();
          this.props.reload();
        },500)

      }
    });

  };

  handleFileList = (obj)=>{
    const pics = new Set();
    obj.forEach((v, k) => {
      if(v.response){
        pics.add(v.response.name);
      }
      if(v.url){
        pics.add(v.url);
      }
    });

    this.setState({
      pics
    })
  }

  render(){

    const {record} = this.props;
    const {
      form: { getFieldDecorator }
    } = this.props;

    return (
      
         {this.showModal()}}>编辑
        {this.handleSave()}}
          onCancel={()=>{this.handleCancel()}}
          destroyOnClose
        >
          
{getFieldDecorator('title',{initialValue:record.title ,rules:[{ required: true, message:"此项为必填项" }]})()} {getFieldDecorator('contact',{initialValue:record.contact ,rules:[{ required: true, message:"此项为必填项" }]})()} {getFieldDecorator('mobile',{initialValue:record.mobile ,rules:[{ required: true, message:"此项为必填项" }]})()} {getFieldDecorator('time',{initialValue:time[record.time],rules:[{ required: true, message:"此项为必填项" }]}) ( )} {getFieldDecorator('rent',{initialValue:record.rent ,rules:[{ required: true, message:"此项为必填项" }]})()} {getFieldDecorator('propertyCost',{initialValue:record.propertyCost ,rules:[{ required: true, message:"此项为必填项" }]})()} {getFieldDecorator('paymentMethod',{initialValue:paymentMethod[record.paymentMethod],rules:[{ required: true, message:"此项为必填项" }]}) ( )} {getFieldDecorator('rentMethod',{initialValue:rentMethod[record.rentMethod],rules:[{ required: true, message:"此项为必填项" }]}) ( )} {getFieldDecorator('coveredArea',{initialValue:record.coveredArea,rules:[{ required: true, message:"此项为必填项" }]})()} {getFieldDecorator('useArea',{initialValue:record.useArea,rules:[{ required: true, message:"此项为必填项" }]})()} {getFieldDecorator('building',{initialValue:`${record.buildingNum}栋${record.buildingUnit}单元${record.buildingFloorNum}号`,rules:[{ required: true, message:"此项为必填项" }]})()} {getFieldDecorator('floor_1',{initialValue:record.floor.toString().split('/')[0],rules:[{ required: true, message:"此项为必填项" }]})()} {getFieldDecorator('floor_2',{initialValue:record.floor.toString().split('/')[1],rules:[{ required: true, message:"此项为必填项" }]})()} {getFieldDecorator('orientation',{initialValue:record.orientation,rules:[{ required: true, message:"此项为必填项"}]}) ( )} {getFieldDecorator('houseType',{initialValue:record.houseType ,rules:[{ required: true, message:"此项为必填项" }]})()} {getFieldDecorator('decoration',{initialValue:decoration[record.decoration],rules:[{ required: true, message:"此项为必填项" }]}) ( )} {getFieldDecorator('facilities',{initialValue:record.facilities.split(','),rules:[{ required: true, message:"此项为必填项" }]}) ( )} {getFieldDecorator('houseDesc',{initialValue:record.houseDesc,rules:[{ required: false}]}) (