新增房产AI

This commit is contained in:
2026-07-30 15:43:44 +08:00
parent e6736d41ff
commit 70354148b4
36 changed files with 2654 additions and 99 deletions

View File

@@ -0,0 +1,46 @@
package com.gxwebsoft.house.controller;
import com.gxwebsoft.common.core.utils.JSONUtil;
import com.gxwebsoft.common.core.web.ApiResult;
import com.gxwebsoft.common.core.web.BaseController;
import com.gxwebsoft.common.core.websocket.WebSocketServer;
import com.gxwebsoft.house.entity.HouseAiChatRequest;
import com.gxwebsoft.house.entity.HouseAiChatResponse;
import com.gxwebsoft.house.service.HouseAiChatService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* AI找房聊天控制器
*/
@Tag(name = "AI找房问答")
@RestController
@RequestMapping("/api/house/ai-chat")
public class HouseAiChatController extends BaseController {
@Resource
private HouseAiChatService houseAiChatService;
@Resource
private WebSocketServer webSocketServer;
@Operation(summary = "发送AI找房问题")
@PostMapping("/message")
public ApiResult<?> message(@RequestBody HouseAiChatRequest request) {
if (request.getUserId() == null || request.getQuestion() == null || request.getQuestion().trim().isEmpty()) {
return fail("提问内容不能为空");
}
try {
HouseAiChatResponse response = houseAiChatService.answer(request);
webSocketServer.sendMessage(String.valueOf(request.getUserId()), JSONUtil.toJSONString(response));
return success("处理成功");
} catch (Exception e) {
return fail("AI服务暂时不可用请稍后再试。");
}
}
}

View File

@@ -0,0 +1,103 @@
package com.gxwebsoft.house.controller;
import com.gxwebsoft.common.core.annotation.OperationLog;
import com.gxwebsoft.common.core.web.ApiResult;
import com.gxwebsoft.common.core.web.BaseController;
import com.gxwebsoft.common.core.web.BatchParam;
import com.gxwebsoft.common.core.web.PageResult;
import com.gxwebsoft.house.entity.HouseAiConfig;
import com.gxwebsoft.house.param.HouseAiConfigParam;
import com.gxwebsoft.house.service.HouseAiConfigService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
/**
* AI找房配置控制器
*/
@Tag(name = "AI找房配置管理")
@RestController
@RequestMapping("/api/house/ai-config")
public class HouseAiConfigController extends BaseController {
@Resource
private HouseAiConfigService houseAiConfigService;
@Operation(summary = "分页查询AI找房配置")
@GetMapping("/page")
public ApiResult<PageResult<HouseAiConfig>> page(HouseAiConfigParam param) {
return success(houseAiConfigService.pageRel(param));
}
@Operation(summary = "查询全部AI找房配置")
@GetMapping()
public ApiResult<List<HouseAiConfig>> list(HouseAiConfigParam param) {
return success(houseAiConfigService.listRel(param));
}
@Operation(summary = "查询当前租户AI找房配置")
@GetMapping("/current")
public ApiResult<HouseAiConfig> current() {
return success(houseAiConfigService.getCurrentConfig(getTenantId()));
}
@Operation(summary = "根据id查询AI找房配置")
@GetMapping("/{id}")
public ApiResult<HouseAiConfig> get(@PathVariable("id") Integer id) {
return success(houseAiConfigService.getByIdRel(id));
}
@OperationLog
@Operation(summary = "添加AI找房配置")
@PostMapping()
public ApiResult<?> save(@RequestBody HouseAiConfig houseAiConfig) {
if (houseAiConfigService.save(houseAiConfig)) {
return success("添加成功");
}
return fail("添加失败");
}
@OperationLog
@Operation(summary = "修改AI找房配置")
@PutMapping()
public ApiResult<?> update(@RequestBody HouseAiConfig houseAiConfig) {
if (houseAiConfigService.updateById(houseAiConfig)) {
return success("修改成功");
}
return fail("修改失败");
}
@OperationLog
@Operation(summary = "删除AI找房配置")
@DeleteMapping("/{id}")
public ApiResult<?> remove(@PathVariable("id") Integer id) {
if (houseAiConfigService.removeById(id)) {
return success("删除成功");
}
return fail("删除失败");
}
@OperationLog
@Operation(summary = "批量修改AI找房配置")
@PutMapping("/batch")
public ApiResult<?> updateBatch(@RequestBody BatchParam<HouseAiConfig> batchParam) {
if (batchParam.update(houseAiConfigService, "config_id")) {
return success("修改成功");
}
return fail("修改失败");
}
@OperationLog
@Operation(summary = "批量删除AI找房配置")
@DeleteMapping("/batch")
public ApiResult<?> removeBatch(@RequestBody List<Integer> ids) {
if (houseAiConfigService.removeByIds(ids)) {
return success("删除成功");
}
return fail("删除失败");
}
}

View File

@@ -0,0 +1,112 @@
package com.gxwebsoft.house.controller;
import com.gxwebsoft.common.core.annotation.OperationLog;
import com.gxwebsoft.common.core.web.ApiResult;
import com.gxwebsoft.common.core.web.BaseController;
import com.gxwebsoft.common.core.web.BatchParam;
import com.gxwebsoft.common.core.web.PageResult;
import com.gxwebsoft.common.system.entity.User;
import com.gxwebsoft.house.entity.HouseFaq;
import com.gxwebsoft.house.param.HouseFaqParam;
import com.gxwebsoft.house.service.HouseFaqService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
/**
* AI找房常见问题控制器
*/
@Tag(name = "AI找房常见问题管理")
@RestController
@RequestMapping("/api/house/house-faq")
public class HouseFaqController extends BaseController {
@Resource
private HouseFaqService houseFaqService;
@Operation(summary = "分页查询AI找房常见问题")
@GetMapping("/page")
public ApiResult<PageResult<HouseFaq>> page(HouseFaqParam param) {
return success(houseFaqService.pageRel(param));
}
@Operation(summary = "查询全部AI找房常见问题")
@GetMapping()
public ApiResult<List<HouseFaq>> list(HouseFaqParam param) {
return success(houseFaqService.listRel(param));
}
@Operation(summary = "根据id查询AI找房常见问题")
@GetMapping("/{id}")
public ApiResult<HouseFaq> get(@PathVariable("id") Integer id) {
return success(houseFaqService.getByIdRel(id));
}
@OperationLog
@Operation(summary = "添加AI找房常见问题")
@PostMapping()
public ApiResult<?> save(@RequestBody HouseFaq houseFaq) {
User loginUser = getLoginUser();
if (loginUser != null) {
houseFaq.setUserId(loginUser.getUserId());
}
if (houseFaqService.save(houseFaq)) {
return success("添加成功");
}
return fail("添加失败");
}
@OperationLog
@Operation(summary = "修改AI找房常见问题")
@PutMapping()
public ApiResult<?> update(@RequestBody HouseFaq houseFaq) {
if (houseFaqService.updateById(houseFaq)) {
return success("修改成功");
}
return fail("修改失败");
}
@OperationLog
@Operation(summary = "删除AI找房常见问题")
@DeleteMapping("/{id}")
public ApiResult<?> remove(@PathVariable("id") Integer id) {
if (houseFaqService.removeById(id)) {
return success("删除成功");
}
return fail("删除失败");
}
@OperationLog
@Operation(summary = "批量添加AI找房常见问题")
@PostMapping("/batch")
public ApiResult<?> saveBatch(@RequestBody List<HouseFaq> list) {
if (houseFaqService.saveBatch(list)) {
return success("添加成功");
}
return fail("添加失败");
}
@OperationLog
@Operation(summary = "批量修改AI找房常见问题")
@PutMapping("/batch")
public ApiResult<?> updateBatch(@RequestBody BatchParam<HouseFaq> batchParam) {
if (batchParam.update(houseFaqService, "faq_id")) {
return success("修改成功");
}
return fail("修改失败");
}
@OperationLog
@Operation(summary = "批量删除AI找房常见问题")
@DeleteMapping("/batch")
public ApiResult<?> removeBatch(@RequestBody List<Integer> ids) {
if (houseFaqService.removeByIds(ids)) {
return success("删除成功");
}
return fail("删除失败");
}
}

View File

@@ -0,0 +1,145 @@
package com.gxwebsoft.house.controller;
import cn.hutool.core.util.StrUtil;
import com.gxwebsoft.common.core.annotation.OperationLog;
import com.gxwebsoft.common.core.web.ApiResult;
import com.gxwebsoft.common.core.web.BaseController;
import com.gxwebsoft.common.core.web.BatchParam;
import com.gxwebsoft.common.core.web.PageResult;
import com.gxwebsoft.common.system.entity.User;
import com.gxwebsoft.house.entity.HouseMessage;
import com.gxwebsoft.house.param.HouseMessageParam;
import com.gxwebsoft.house.service.HouseMessageService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
import java.util.regex.Pattern;
/**
* AI找房留言控制器
*/
@Tag(name = "AI找房留言管理")
@RestController
@RequestMapping("/api/house/house-message")
public class HouseMessageController extends BaseController {
private static final Pattern PHONE_PATTERN = Pattern.compile("^1[3-9]\\d{9}$");
private static final Pattern WECHAT_PATTERN = Pattern.compile("^[a-zA-Z][-_a-zA-Z0-9]{5,19}$");
@Resource
private HouseMessageService houseMessageService;
@Operation(summary = "分页查询AI找房留言")
@GetMapping("/page")
public ApiResult<PageResult<HouseMessage>> page(HouseMessageParam param) {
return success(houseMessageService.pageRel(param));
}
@Operation(summary = "查询全部AI找房留言")
@GetMapping()
public ApiResult<List<HouseMessage>> list(HouseMessageParam param) {
return success(houseMessageService.listRel(param));
}
@Operation(summary = "根据id查询AI找房留言")
@GetMapping("/{id}")
public ApiResult<HouseMessage> get(@PathVariable("id") Integer id) {
return success(houseMessageService.getByIdRel(id));
}
@OperationLog
@Operation(summary = "添加AI找房留言")
@PostMapping()
public ApiResult<?> save(@RequestBody HouseMessage houseMessage) {
String error = validateMessage(houseMessage);
if (error != null) {
return fail(error);
}
User loginUser = getLoginUser();
if (loginUser != null) {
houseMessage.setUserId(loginUser.getUserId());
}
houseMessage.setRealName(houseMessage.getRealName().trim());
houseMessage.setPhone(StrUtil.trimToNull(houseMessage.getPhone()));
houseMessage.setWechat(StrUtil.trimToNull(houseMessage.getWechat()));
houseMessage.setSource(StrUtil.blankToDefault(houseMessage.getSource(), "ai_house"));
if (houseMessage.getStatus() == null) {
houseMessage.setStatus(0);
}
if (houseMessageService.save(houseMessage)) {
return success("提交成功");
}
return fail("提交失败");
}
@OperationLog
@Operation(summary = "修改AI找房留言")
@PutMapping()
public ApiResult<?> update(@RequestBody HouseMessage houseMessage) {
String error = validateMessage(houseMessage);
if (error != null) {
return fail(error);
}
houseMessage.setRealName(houseMessage.getRealName().trim());
houseMessage.setPhone(StrUtil.trimToNull(houseMessage.getPhone()));
houseMessage.setWechat(StrUtil.trimToNull(houseMessage.getWechat()));
if (houseMessageService.updateById(houseMessage)) {
return success("修改成功");
}
return fail("修改失败");
}
@OperationLog
@Operation(summary = "删除AI找房留言")
@DeleteMapping("/{id}")
public ApiResult<?> remove(@PathVariable("id") Integer id) {
if (houseMessageService.removeById(id)) {
return success("删除成功");
}
return fail("删除失败");
}
@OperationLog
@Operation(summary = "批量修改AI找房留言")
@PutMapping("/batch")
public ApiResult<?> updateBatch(@RequestBody BatchParam<HouseMessage> batchParam) {
if (batchParam.update(houseMessageService, "message_id")) {
return success("修改成功");
}
return fail("修改失败");
}
@OperationLog
@Operation(summary = "批量删除AI找房留言")
@DeleteMapping("/batch")
public ApiResult<?> removeBatch(@RequestBody List<Integer> ids) {
if (houseMessageService.removeByIds(ids)) {
return success("删除成功");
}
return fail("删除失败");
}
private String validateMessage(HouseMessage houseMessage) {
if (houseMessage == null || StrUtil.isBlank(houseMessage.getRealName())) {
return "请输入姓名";
}
if (houseMessage.getRealName().trim().length() > 30) {
return "姓名不能超过30个字符";
}
boolean hasPhone = StrUtil.isNotBlank(houseMessage.getPhone());
boolean hasWechat = StrUtil.isNotBlank(houseMessage.getWechat());
if (!hasPhone && !hasWechat) {
return "手机号和微信号请至少填写一项";
}
if (hasPhone && !PHONE_PATTERN.matcher(houseMessage.getPhone().trim()).matches()) {
return "手机号格式不正确";
}
if (hasWechat && !WECHAT_PATTERN.matcher(houseMessage.getWechat().trim()).matches()) {
return "微信号格式不正确";
}
return null;
}
}

View File

@@ -0,0 +1,24 @@
package com.gxwebsoft.house.entity;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
/**
* AI找房提问请求
*/
@Data
@Schema(name = "HouseAiChatRequest对象", description = "AI找房提问请求")
public class HouseAiChatRequest implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "会话ID")
private String conversationId;
@Schema(description = "用户ID")
private Integer userId;
@Schema(description = "问题")
private String question;
}

View File

@@ -0,0 +1,35 @@
package com.gxwebsoft.house.entity;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
* AI找房应答
*/
@Data
@Schema(name = "HouseAiChatResponse对象", description = "AI找房应答")
public class HouseAiChatResponse implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "消息类型")
private String type = "house_ai_result";
@Schema(description = "回答文本")
private String answer;
@Schema(description = "命中的常见问题")
private List<HouseFaq> faqs = new ArrayList<>();
@Schema(description = "推荐房源")
private List<HouseAiHouseCard> houses = new ArrayList<>();
@Schema(description = "语义解析结果")
private HouseAiIntent intent;
@Schema(description = "来源 faq/house/ai")
private String source;
}

View File

@@ -0,0 +1,53 @@
package com.gxwebsoft.house.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
import java.util.Date;
/**
* AI找房配置
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Schema(name = "HouseAiConfig对象", description = "AI找房配置")
public class HouseAiConfig implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "主键ID")
@TableId(value = "config_id", type = IdType.AUTO)
private Integer configId;
@Schema(description = "AI形象照")
private String aiAvatar;
@Schema(description = "首页AI入口图")
private String aiEntryImage;
@Schema(description = "首页AI悬浮图")
private String aiFloatImage;
@Schema(description = "欢迎词")
private String welcomeMessage;
@Schema(description = "状态 0正常 1禁用")
private Integer status;
@Schema(description = "是否删除, 0否, 1是")
@TableLogic
private Integer deleted;
@Schema(description = "租户ID")
private Integer tenantId;
@Schema(description = "创建时间")
private Date createTime;
@Schema(description = "更新时间")
private Date updateTime;
}

View File

@@ -0,0 +1,51 @@
package com.gxwebsoft.house.entity;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
/**
* AI找房返回的轻量房源卡片
*/
@Data
@Schema(name = "HouseAiHouseCard对象", description = "AI找房返回的轻量房源卡片")
public class HouseAiHouseCard implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "房源ID")
private Integer houseId;
@Schema(description = "房源标题")
private String houseTitle;
@Schema(description = "户型")
private String houseType;
@Schema(description = "面积")
private String extent;
@Schema(description = "楼层")
private String floor;
@Schema(description = "朝向")
private String toward;
@Schema(description = "月租金")
private String monthlyRent;
@Schema(description = "所在城市")
private String city;
@Schema(description = "所在辖区")
private String region;
@Schema(description = "详细地址")
private String address;
@Schema(description = "图片附件")
private String files;
@Schema(description = "办公室配套")
private String supporting;
}

View File

@@ -0,0 +1,87 @@
package com.gxwebsoft.house.entity;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
/**
* AI找房语义解析结果
*/
@Data
@Schema(name = "HouseAiIntent对象", description = "AI找房语义解析结果")
public class HouseAiIntent implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "原始问题")
private String originalQuestion;
@Schema(description = "意图类型 faq/house/mixed/unknown")
private String intentType;
@Schema(description = "归一化问题")
private String normalizedQuestion;
@Schema(description = "面积最小值")
private Integer extentMin;
@Schema(description = "面积最大值")
private Integer extentMax;
@Schema(description = "楼层最小值")
private Integer floorMin;
@Schema(description = "楼层最大值")
private Integer floorMax;
@Schema(description = "月租最小值")
private BigDecimal monthlyRentMin;
@Schema(description = "月租最大值")
private BigDecimal monthlyRentMax;
@Schema(description = "售价最小值")
private BigDecimal salePriceMin;
@Schema(description = "售价最大值")
private BigDecimal salePriceMax;
@Schema(description = "总价最小值")
private BigDecimal totalPriceMin;
@Schema(description = "总价最大值")
private BigDecimal totalPriceMax;
@Schema(description = "区域/地段")
private String regionKeyword;
@Schema(description = "城市")
private String cityKeyword;
@Schema(description = "租售类型 rent/sale")
private String tradeType;
@Schema(description = "装修类型")
private String decorationType;
@Schema(description = "配套要求")
private String supportingKeyword;
@Schema(description = "朝向")
private String toward;
@Schema(description = "房型")
private String houseType;
@Schema(description = "AI生成的SQL筛选条件片段")
private String whereSql;
@Schema(description = "AI生成的SQL排序片段")
private String orderSql;
@Schema(description = "其他关键词")
private List<String> tags = new ArrayList<>();
}

View File

@@ -0,0 +1,59 @@
package com.gxwebsoft.house.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
import java.util.Date;
/**
* AI找房常见问题
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Schema(name = "HouseFaq对象", description = "AI找房常见问题")
public class HouseFaq implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "主键ID")
@TableId(value = "faq_id", type = IdType.AUTO)
private Integer faqId;
@Schema(description = "问题")
private String question;
@Schema(description = "关键词,多个用逗号分隔")
private String keywords;
@Schema(description = "标准回答")
private String answer;
@Schema(description = "分类")
private String category;
@Schema(description = "排序号")
private Integer sortNumber;
@Schema(description = "状态 0正常 1禁用")
private Integer status;
@Schema(description = "创建用户ID")
private Integer userId;
@Schema(description = "是否删除, 0否, 1是")
@TableLogic
private Integer deleted;
@Schema(description = "租户ID")
private Integer tenantId;
@Schema(description = "创建时间")
private Date createTime;
@Schema(description = "更新时间")
private Date updateTime;
}

View File

@@ -0,0 +1,62 @@
package com.gxwebsoft.house.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* AI找房留言
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Schema(name = "HouseMessage对象", description = "AI找房留言")
public class HouseMessage implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "主键ID")
@TableId(value = "message_id", type = IdType.AUTO)
private Integer messageId;
@Schema(description = "用户ID")
private Integer userId;
@Schema(description = "姓名")
private String realName;
@Schema(description = "手机号")
private String phone;
@Schema(description = "微信号")
private String wechat;
@Schema(description = "来源")
private String source;
@Schema(description = "备注")
private String comments;
@Schema(description = "状态 0未处理 1已处理")
private Integer status;
@Schema(description = "是否删除, 0否, 1是")
@TableLogic
private Integer deleted;
@Schema(description = "租户ID")
private Integer tenantId;
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
@Schema(description = "更新时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;
}

View File

@@ -0,0 +1,19 @@
package com.gxwebsoft.house.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.gxwebsoft.house.entity.HouseAiConfig;
import com.gxwebsoft.house.param.HouseAiConfigParam;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* AI找房配置Mapper
*/
public interface HouseAiConfigMapper extends BaseMapper<HouseAiConfig> {
List<HouseAiConfig> selectPageRel(@Param("page") IPage<HouseAiConfig> page, @Param("param") HouseAiConfigParam param);
List<HouseAiConfig> selectListRel(@Param("param") HouseAiConfigParam param);
}

View File

@@ -0,0 +1,19 @@
package com.gxwebsoft.house.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.gxwebsoft.house.entity.HouseFaq;
import com.gxwebsoft.house.param.HouseFaqParam;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* AI找房常见问题Mapper
*/
public interface HouseFaqMapper extends BaseMapper<HouseFaq> {
List<HouseFaq> selectPageRel(@Param("page") IPage<HouseFaq> page, @Param("param") HouseFaqParam param);
List<HouseFaq> selectListRel(@Param("param") HouseFaqParam param);
}

View File

@@ -34,4 +34,9 @@ public interface HouseInfoMapper extends BaseMapper<HouseInfo> {
*/
List<HouseInfo> selectListRel(@Param("param") HouseInfoParam param);
/**
* 执行AI生成的受控查询条件
*/
List<HouseInfo> selectListByAiSql(@Param("whereSql") String whereSql, @Param("orderSql") String orderSql);
}

View File

@@ -0,0 +1,19 @@
package com.gxwebsoft.house.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.gxwebsoft.house.entity.HouseMessage;
import com.gxwebsoft.house.param.HouseMessageParam;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* AI找房留言Mapper
*/
public interface HouseMessageMapper extends BaseMapper<HouseMessage> {
List<HouseMessage> selectPageRel(@Param("page") IPage<HouseMessage> page, @Param("param") HouseMessageParam param);
List<HouseMessage> selectListRel(@Param("param") HouseMessageParam param);
}

View File

@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.gxwebsoft.house.mapper.HouseAiConfigMapper">
<sql id="selectSql">
SELECT a.*
FROM house_ai_config a
<where>
<if test="param.configId != null">
AND a.config_id = #{param.configId}
</if>
<if test="param.aiAvatar != null and param.aiAvatar != ''">
AND a.ai_avatar LIKE CONCAT('%', #{param.aiAvatar}, '%')
</if>
<if test="param.aiEntryImage != null and param.aiEntryImage != ''">
AND a.ai_entry_image LIKE CONCAT('%', #{param.aiEntryImage}, '%')
</if>
<if test="param.aiFloatImage != null and param.aiFloatImage != ''">
AND a.ai_float_image LIKE CONCAT('%', #{param.aiFloatImage}, '%')
</if>
<if test="param.welcomeMessage != null and param.welcomeMessage != ''">
AND a.welcome_message LIKE CONCAT('%', #{param.welcomeMessage}, '%')
</if>
<if test="param.status != null">
AND a.status = #{param.status}
</if>
<if test="param.deleted != null">
AND a.deleted = #{param.deleted}
</if>
<if test="param.deleted == null">
AND a.deleted = 0
</if>
<if test="param.tenantId != null">
AND a.tenant_id = #{param.tenantId}
</if>
<if test="param.keywords != null and param.keywords != ''">
AND (
a.welcome_message LIKE CONCAT('%', #{param.keywords}, '%')
OR a.ai_avatar LIKE CONCAT('%', #{param.keywords}, '%')
OR a.ai_entry_image LIKE CONCAT('%', #{param.keywords}, '%')
OR a.ai_float_image LIKE CONCAT('%', #{param.keywords}, '%')
)
</if>
</where>
ORDER BY a.config_id DESC
</sql>
<select id="selectPageRel" resultType="com.gxwebsoft.house.entity.HouseAiConfig">
<include refid="selectSql"></include>
</select>
<select id="selectListRel" resultType="com.gxwebsoft.house.entity.HouseAiConfig">
<include refid="selectSql"></include>
</select>
</mapper>

View File

@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.gxwebsoft.house.mapper.HouseFaqMapper">
<sql id="selectSql">
SELECT a.*
FROM house_faq a
<where>
<if test="param.faqId != null">
AND a.faq_id = #{param.faqId}
</if>
<if test="param.question != null and param.question != ''">
AND a.question LIKE CONCAT('%', #{param.question}, '%')
</if>
<if test="param.keywordsText != null and param.keywordsText != ''">
AND a.keywords LIKE CONCAT('%', #{param.keywordsText}, '%')
</if>
<if test="param.answer != null and param.answer != ''">
AND a.answer LIKE CONCAT('%', #{param.answer}, '%')
</if>
<if test="param.category != null and param.category != ''">
AND a.category LIKE CONCAT('%', #{param.category}, '%')
</if>
<if test="param.sortNumber != null">
AND a.sort_number = #{param.sortNumber}
</if>
<if test="param.status != null">
AND a.status = #{param.status}
</if>
<if test="param.userId != null">
AND a.user_id = #{param.userId}
</if>
<if test="param.deleted != null">
AND a.deleted = #{param.deleted}
</if>
<if test="param.deleted == null">
AND a.deleted = 0
</if>
<if test="param.createTimeStart != null">
AND a.create_time &gt;= #{param.createTimeStart}
</if>
<if test="param.createTimeEnd != null">
AND a.create_time &lt;= #{param.createTimeEnd}
</if>
<if test="param.keywords != null and param.keywords != ''">
AND (
a.question LIKE CONCAT('%', #{param.keywords}, '%')
OR a.keywords LIKE CONCAT('%', #{param.keywords}, '%')
OR a.answer LIKE CONCAT('%', #{param.keywords}, '%')
OR a.category LIKE CONCAT('%', #{param.keywords}, '%')
)
</if>
</where>
ORDER BY a.sort_number ASC, a.faq_id DESC
</sql>
<select id="selectPageRel" resultType="com.gxwebsoft.house.entity.HouseFaq">
<include refid="selectSql"></include>
</select>
<select id="selectListRel" resultType="com.gxwebsoft.house.entity.HouseFaq">
<include refid="selectSql"></include>
</select>
</mapper>

View File

@@ -172,4 +172,25 @@
<include refid="selectSql"></include>
</select>
<!-- AI受控查询 -->
<select id="selectListByAiSql" resultType="com.gxwebsoft.house.entity.HouseInfo">
SELECT a.*,
b.nickname,b.avatar,b.grade_id, b.phone as userPhone
FROM house_info a
LEFT JOIN gxwebsoft_core.sys_user b ON a.user_id = b.user_id
<where>
a.deleted = 0
AND a.status = 0
<if test="whereSql != null and whereSql != ''">
AND ${whereSql}
</if>
</where>
<if test="orderSql != null and orderSql != ''">
ORDER BY ${orderSql}
</if>
<if test="orderSql == null or orderSql == ''">
ORDER BY a.sort_number asc, a.create_time desc
</if>
</select>
</mapper>

View File

@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.gxwebsoft.house.mapper.HouseMessageMapper">
<sql id="selectSql">
SELECT a.*
FROM house_message a
<where>
<if test="param.messageId != null">
AND a.message_id = #{param.messageId}
</if>
<if test="param.userId != null">
AND a.user_id = #{param.userId}
</if>
<if test="param.realName != null and param.realName != ''">
AND a.real_name LIKE CONCAT('%', #{param.realName}, '%')
</if>
<if test="param.phone != null and param.phone != ''">
AND a.phone LIKE CONCAT('%', #{param.phone}, '%')
</if>
<if test="param.wechat != null and param.wechat != ''">
AND a.wechat LIKE CONCAT('%', #{param.wechat}, '%')
</if>
<if test="param.source != null and param.source != ''">
AND a.source = #{param.source}
</if>
<if test="param.comments != null and param.comments != ''">
AND a.comments LIKE CONCAT('%', #{param.comments}, '%')
</if>
<if test="param.status != null">
AND a.status = #{param.status}
</if>
<if test="param.deleted != null">
AND a.deleted = #{param.deleted}
</if>
<if test="param.deleted == null">
AND a.deleted = 0
</if>
<if test="param.createTimeStart != null">
AND a.create_time &gt;= #{param.createTimeStart}
</if>
<if test="param.createTimeEnd != null">
AND a.create_time &lt;= #{param.createTimeEnd}
</if>
<if test="param.keywords != null and param.keywords != ''">
AND (
a.real_name LIKE CONCAT('%', #{param.keywords}, '%')
OR a.phone LIKE CONCAT('%', #{param.keywords}, '%')
OR a.wechat LIKE CONCAT('%', #{param.keywords}, '%')
OR a.comments LIKE CONCAT('%', #{param.keywords}, '%')
)
</if>
</where>
</sql>
<select id="selectPageRel" resultType="com.gxwebsoft.house.entity.HouseMessage">
<include refid="selectSql"></include>
</select>
<select id="selectListRel" resultType="com.gxwebsoft.house.entity.HouseMessage">
<include refid="selectSql"></include>
</select>
</mapper>

View File

@@ -0,0 +1,44 @@
package com.gxwebsoft.house.param;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.gxwebsoft.common.core.annotation.QueryField;
import com.gxwebsoft.common.core.annotation.QueryType;
import com.gxwebsoft.common.core.web.BaseParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* AI找房配置查询参数
*/
@Data
@EqualsAndHashCode(callSuper = false)
@JsonInclude(JsonInclude.Include.NON_NULL)
@Schema(name = "HouseAiConfigParam对象", description = "AI找房配置查询参数")
public class HouseAiConfigParam extends BaseParam {
private static final long serialVersionUID = 1L;
@Schema(description = "主键ID")
@QueryField(type = QueryType.EQ)
private Integer configId;
@Schema(description = "AI形象照")
private String aiAvatar;
@Schema(description = "首页AI入口图")
private String aiEntryImage;
@Schema(description = "首页AI悬浮图")
private String aiFloatImage;
@Schema(description = "欢迎词")
private String welcomeMessage;
@Schema(description = "状态 0正常 1禁用")
@QueryField(type = QueryType.EQ)
private Integer status;
@Schema(description = "是否删除, 0否, 1是")
@QueryField(type = QueryType.EQ)
private Integer deleted;
}

View File

@@ -0,0 +1,57 @@
package com.gxwebsoft.house.param;
import com.baomidou.mybatisplus.annotation.TableField;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.gxwebsoft.common.core.annotation.QueryField;
import com.gxwebsoft.common.core.annotation.QueryType;
import com.gxwebsoft.common.core.web.BaseParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* AI找房常见问题查询参数
*/
@Data
@EqualsAndHashCode(callSuper = false)
@JsonInclude(JsonInclude.Include.NON_NULL)
@Schema(name = "HouseFaqParam对象", description = "AI找房常见问题查询参数")
public class HouseFaqParam extends BaseParam {
private static final long serialVersionUID = 1L;
@Schema(description = "主键ID")
@QueryField(type = QueryType.EQ)
private Integer faqId;
@Schema(description = "问题")
private String question;
@Schema(description = "关键词")
private String keywordsText;
@Schema(description = "回答")
private String answer;
@Schema(description = "分类")
private String category;
@Schema(description = "排序号")
@QueryField(type = QueryType.EQ)
private Integer sortNumber;
@Schema(description = "状态 0正常 1禁用")
@QueryField(type = QueryType.EQ)
private Integer status;
@Schema(description = "创建用户ID")
@QueryField(type = QueryType.EQ)
private Integer userId;
@Schema(description = "是否删除, 0否, 1是")
@QueryField(type = QueryType.EQ)
private Integer deleted;
@Schema(description = "语义搜索原句")
@TableField(exist = false)
private String queryText;
}

View File

@@ -0,0 +1,54 @@
package com.gxwebsoft.house.param;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.gxwebsoft.common.core.annotation.QueryField;
import com.gxwebsoft.common.core.annotation.QueryType;
import com.gxwebsoft.common.core.web.BaseParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* AI找房留言查询参数
*/
@Data
@EqualsAndHashCode(callSuper = false)
@JsonInclude(JsonInclude.Include.NON_NULL)
@Schema(name = "HouseMessageParam对象", description = "AI找房留言查询参数")
public class HouseMessageParam extends BaseParam {
private static final long serialVersionUID = 1L;
@Schema(description = "主键ID")
@QueryField(type = QueryType.EQ)
private Integer messageId;
@Schema(description = "用户ID")
@QueryField(type = QueryType.EQ)
private Integer userId;
@Schema(description = "姓名")
private String realName;
@Schema(description = "手机号")
private String phone;
@Schema(description = "微信号")
private String wechat;
@Schema(description = "来源")
private String source;
@Schema(description = "备注")
private String comments;
@Schema(description = "状态 0未处理 1已处理")
@QueryField(type = QueryType.EQ)
private Integer status;
@Schema(description = "是否删除, 0否, 1是")
@QueryField(type = QueryType.EQ)
private Integer deleted;
@Schema(description = "关键词")
private String keywords;
}

View File

@@ -0,0 +1,15 @@
package com.gxwebsoft.house.service;
import com.gxwebsoft.house.entity.HouseAiChatRequest;
import com.gxwebsoft.house.entity.HouseAiChatResponse;
import com.gxwebsoft.house.entity.HouseAiIntent;
/**
* AI找房问答Service
*/
public interface HouseAiChatService {
HouseAiIntent analyzeIntent(String question);
HouseAiChatResponse answer(HouseAiChatRequest request);
}

View File

@@ -0,0 +1,22 @@
package com.gxwebsoft.house.service;
import com.github.yulichang.base.MPJBaseService;
import com.gxwebsoft.common.core.web.PageResult;
import com.gxwebsoft.house.entity.HouseAiConfig;
import com.gxwebsoft.house.param.HouseAiConfigParam;
import java.util.List;
/**
* AI找房配置Service
*/
public interface HouseAiConfigService extends MPJBaseService<HouseAiConfig> {
PageResult<HouseAiConfig> pageRel(HouseAiConfigParam param);
List<HouseAiConfig> listRel(HouseAiConfigParam param);
HouseAiConfig getByIdRel(Integer configId);
HouseAiConfig getCurrentConfig(Integer tenantId);
}

View File

@@ -0,0 +1,22 @@
package com.gxwebsoft.house.service;
import com.github.yulichang.base.MPJBaseService;
import com.gxwebsoft.common.core.web.PageResult;
import com.gxwebsoft.house.entity.HouseFaq;
import com.gxwebsoft.house.param.HouseFaqParam;
import java.util.List;
/**
* AI找房常见问题Service
*/
public interface HouseFaqService extends MPJBaseService<HouseFaq> {
PageResult<HouseFaq> pageRel(HouseFaqParam param);
List<HouseFaq> listRel(HouseFaqParam param);
HouseFaq getByIdRel(Integer faqId);
List<HouseFaq> findBestMatches(String queryText, int limit);
}

View File

@@ -0,0 +1,20 @@
package com.gxwebsoft.house.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.gxwebsoft.common.core.web.PageResult;
import com.gxwebsoft.house.entity.HouseMessage;
import com.gxwebsoft.house.param.HouseMessageParam;
import java.util.List;
/**
* AI找房留言Service
*/
public interface HouseMessageService extends IService<HouseMessage> {
PageResult<HouseMessage> pageRel(HouseMessageParam param);
List<HouseMessage> listRel(HouseMessageParam param);
HouseMessage getByIdRel(Integer messageId);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,52 @@
package com.gxwebsoft.house.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.gxwebsoft.common.core.web.PageParam;
import com.gxwebsoft.common.core.web.PageResult;
import com.gxwebsoft.house.entity.HouseAiConfig;
import com.gxwebsoft.house.mapper.HouseAiConfigMapper;
import com.gxwebsoft.house.param.HouseAiConfigParam;
import com.gxwebsoft.house.service.HouseAiConfigService;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* AI找房配置Service实现
*/
@Service
public class HouseAiConfigServiceImpl extends ServiceImpl<HouseAiConfigMapper, HouseAiConfig> implements HouseAiConfigService {
@Override
public PageResult<HouseAiConfig> pageRel(HouseAiConfigParam param) {
PageParam<HouseAiConfig, HouseAiConfigParam> page = new PageParam<>(param);
page.setDefaultOrder("config_id desc");
List<HouseAiConfig> list = baseMapper.selectPageRel(page, param);
return new PageResult<>(list, page.getTotal());
}
@Override
public List<HouseAiConfig> listRel(HouseAiConfigParam param) {
return baseMapper.selectListRel(param);
}
@Override
public HouseAiConfig getByIdRel(Integer configId) {
HouseAiConfigParam param = new HouseAiConfigParam();
param.setConfigId(configId);
return param.getOne(baseMapper.selectListRel(param));
}
@Override
public HouseAiConfig getCurrentConfig(Integer tenantId) {
HouseAiConfigParam param = new HouseAiConfigParam();
param.setStatus(0);
param.setTenantId(tenantId);
HouseAiConfig config = param.getOne(baseMapper.selectListRel(param));
if (config != null) {
return config;
}
param.setTenantId(null);
return param.getOne(baseMapper.selectListRel(param));
}
}

View File

@@ -0,0 +1,151 @@
package com.gxwebsoft.house.service.impl;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.gxwebsoft.common.core.web.PageParam;
import com.gxwebsoft.common.core.web.PageResult;
import com.gxwebsoft.house.entity.HouseFaq;
import com.gxwebsoft.house.mapper.HouseFaqMapper;
import com.gxwebsoft.house.param.HouseFaqParam;
import com.gxwebsoft.house.service.HouseFaqService;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;
/**
* AI找房常见问题Service实现
*/
@Service
public class HouseFaqServiceImpl extends ServiceImpl<HouseFaqMapper, HouseFaq> implements HouseFaqService {
@Override
public PageResult<HouseFaq> pageRel(HouseFaqParam param) {
PageParam<HouseFaq, HouseFaqParam> page = new PageParam<>(param);
page.setDefaultOrder("sort_number asc, faq_id desc");
List<HouseFaq> list = baseMapper.selectPageRel(page, param);
return new PageResult<>(list, page.getTotal());
}
@Override
public List<HouseFaq> listRel(HouseFaqParam param) {
return baseMapper.selectListRel(param);
}
@Override
public HouseFaq getByIdRel(Integer faqId) {
HouseFaqParam param = new HouseFaqParam();
param.setFaqId(faqId);
return param.getOne(baseMapper.selectListRel(param));
}
@Override
public List<HouseFaq> findBestMatches(String queryText, int limit) {
HouseFaqParam param = new HouseFaqParam();
param.setStatus(0);
List<HouseFaq> all = baseMapper.selectListRel(param);
if (StrUtil.isBlank(queryText) || all == null || all.isEmpty()) {
return new ArrayList<>();
}
final String normalizedQuery = normalizeText(queryText);
return all.stream()
.map(item -> new ScoredFaq(item, score(item, normalizedQuery)))
.filter(item -> item.score > 0)
.sorted(Comparator.comparingInt(ScoredFaq::getScore).reversed()
.thenComparing(item -> item.faq.getSortNumber() == null ? Integer.MAX_VALUE : item.faq.getSortNumber()))
.limit(limit)
.map(ScoredFaq::getFaq)
.collect(Collectors.toList());
}
private int score(HouseFaq faq, String normalizedQuery) {
int score = 0;
String question = normalizeText(faq.getQuestion());
String keywords = normalizeText(faq.getKeywords());
String answer = normalizeText(faq.getAnswer());
String category = normalizeText(faq.getCategory());
if (StrUtil.isBlank(normalizedQuery)) {
return score;
}
if (question.contains(normalizedQuery)) {
score += 120;
}
if (keywords.contains(normalizedQuery)) {
score += 100;
}
if (answer.contains(normalizedQuery)) {
score += 40;
}
if (category.contains(normalizedQuery)) {
score += 30;
}
for (String token : tokenize(normalizedQuery)) {
if (token.length() < 2) {
continue;
}
if (question.contains(token)) {
score += 20;
}
if (keywords.contains(token)) {
score += 18;
}
if (answer.contains(token)) {
score += 8;
}
if (category.contains(token)) {
score += 6;
}
}
return score;
}
private List<String> tokenize(String text) {
String normalized = normalizeText(text);
List<String> tokens = new ArrayList<>();
for (String item : normalized.split("[,\\s+/|]+")) {
if (StrUtil.isNotBlank(item)) {
tokens.add(item);
}
}
return tokens;
}
private String normalizeText(String text) {
if (text == null) {
return "";
}
return text.replace(" ", " ")
.replace("", ",")
.replace("", " ")
.replace("", " ")
.replace("", " ")
.replace("", "(")
.replace("", ")")
.replace("", "")
.replace("", "")
.replace("", "")
.toLowerCase(Locale.ROOT)
.trim();
}
private static class ScoredFaq {
private final HouseFaq faq;
private final int score;
private ScoredFaq(HouseFaq faq, int score) {
this.faq = faq;
this.score = score;
}
public HouseFaq getFaq() {
return faq;
}
public int getScore() {
return score;
}
}
}

View File

@@ -0,0 +1,42 @@
package com.gxwebsoft.house.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.gxwebsoft.common.core.web.PageParam;
import com.gxwebsoft.common.core.web.PageResult;
import com.gxwebsoft.house.entity.HouseMessage;
import com.gxwebsoft.house.mapper.HouseMessageMapper;
import com.gxwebsoft.house.param.HouseMessageParam;
import com.gxwebsoft.house.service.HouseMessageService;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* AI找房留言Service实现
*/
@Service
public class HouseMessageServiceImpl extends ServiceImpl<HouseMessageMapper, HouseMessage> implements HouseMessageService {
@Override
public PageResult<HouseMessage> pageRel(HouseMessageParam param) {
PageParam<HouseMessage, HouseMessageParam> page = new PageParam<>(param);
page.setDefaultOrder("create_time desc, message_id desc");
List<HouseMessage> list = baseMapper.selectPageRel(page, param);
return new PageResult<>(list, page.getTotal());
}
@Override
public List<HouseMessage> listRel(HouseMessageParam param) {
List<HouseMessage> list = baseMapper.selectListRel(param);
PageParam<HouseMessage, HouseMessageParam> page = new PageParam<>();
page.setDefaultOrder("create_time desc, message_id desc");
return page.sortRecords(list);
}
@Override
public HouseMessage getByIdRel(Integer messageId) {
HouseMessageParam param = new HouseMessageParam();
param.setMessageId(messageId);
return param.getOne(baseMapper.selectListRel(param));
}
}