从零开始复现小智AI 03

在上一次复现中我成功完成用户登录相关逻辑在登录后前端会自动发送两个请求查询后端相关数据http://localhost:8084/api/device/query和http://localhost:8084/api/message/query?start=1&limit=10,本次复现就实现这两个请求,同时我也购买了小智ai的硬件设备,本文也会详细描述我在将硬件也连上本机后端的步骤,以及踩的坑。

前端请求处理

我发现源码在处理请求之前会调用PageFilter限制分页查询的每页最大数,开始的时候我打算使用责任链的方式实现代理模式设计实现请求的过滤,但是我发现分页查询的逻辑很简单,并且有很多其他的分页查询请求也会需要过滤,随后我又想用aop环绕通知的方式去后处理查询结果,但是再数据量大的情况下这可能导致内存溢出。综上我决定还是使用简单的工具类解决办法,把控制分页的逻辑写在业务代码中。在types层创建如下工具类。

public class PageFilterUtils {

    /**
     * 最大分页数量
     */
    public static final int MAX_PAGE_SIZE = 1000;
    /**
     * 默认开始页
     */
    public static final int PAGE_NUM = 1;
    /**
     * 默认每页数量
     */
    public static final int PAGE_SIZE = 10;


    public static void pageFilter(HttpServletRequest request) {


        String startParam = request.getParameter("start");
        String limitParam = request.getParameter("limit");

        int pageNum = StringUtils.hasText(startParam) ? Integer.parseInt(startParam) : PAGE_NUM;
        int pageSize = StringUtils.hasText(limitParam) ? Integer.parseInt(limitParam) : PAGE_SIZE;

        // 限制最大页大小
        pageSize = Math.min(pageSize, MAX_PAGE_SIZE);

        // 调用 PageHelper 开启分页
        PageHelper.startPage(pageNum, pageSize);
    }
}

在设计过滤逻辑的过程中我遇到了如下问题:

PageHelper仅能影响到紧跟着的一次数据库查询用于限制查询条数,源代码中关于分页查询的代码大多是多表联查因此PageHelper是有作用的,但是由于我的代码框架是ddd结构的需要尽量的避免多表联查实现解耦。一开始的时候我并不了解PageHelper的作用之处,在Controller开头就调用了pageFilter方法,这样会导致无法实现针对性的分页查询。\

对于前端的两个请求处理过程比较类似,这里我就贴上只其中一个的代码了

//API层的代码
@RequestMapping("/api/message")
@Tag(name = "消息管理", description = "消息相关操作")
public interface IMessageController {

    @GetMapping("/query")
    @Operation(summary = "根据条件查询对话消息", description = "返回对话消息列表")
    Response<PageInfo<MessageResponseDTO>> query(HttpServletRequest request);

}
//Trigger层的实现
@Slf4j
@RestController
public class MessageController implements IMessageController {
    @Resource
    private IDeviceService deviceService;

    @Resource
    private IMessageService messageService;

    @Resource
    private IRoleService roleService;


    @Override
    public Response<PageInfo<MessageResponseDTO>> query(HttpServletRequest request) {

        try {
            Integer userId = UserContextUtils.getCurrentUserId();

            //查询用户持有的设备列表
            List<DeviceEntity> deviceEntityList=deviceService.queryBuyUserId(userId);
            List<String> deviceIds = deviceEntityList.stream().map(DeviceEntity::getDeviceId).toList();
            List<Integer> roleIds = deviceEntityList.stream().map(DeviceEntity::getRoleId).toList();


            PageFilterUtils.pageFilter(request);
            List<MessageEntity> messageEntityList=messageService.queryMessageBuyDeviceIds(deviceIds);

            List<DashboardRoleVO> roleVOList = roleService.queryRoleBuyRoleIdsAndUserId(userId, roleIds);

            List<MessageResponseDTO> messageResponseDTOList = new ArrayList<>();
            for (MessageEntity messageEntity : messageEntityList) {
                Integer roleId = messageEntity.getRoleId();
                String deviceId = messageEntity.getDeviceId();
                String roleName = roleVOList.stream()
                        .filter(roleVO -> roleVO.getRoleId().equals(roleId))
                        .map(DashboardRoleVO::getRoleName)
                        .findFirst()
                        .orElse(null);
                String deviceName = deviceEntityList.stream()
                        .filter(deviceEntity -> deviceEntity.getDeviceId().equals(deviceId))
                        .map(DeviceEntity::getDeviceName)
                        .findFirst()
                        .orElse(null);


                messageEntity.setRoleName(roleName);
                messageEntity.setDeviceName(deviceName);
                MessageResponseDTO messageResponseDTO = new MessageResponseDTO();
                BeanUtils.copyProperties(messageEntity,messageResponseDTO);
                messageResponseDTOList.add(messageResponseDTO);

            }


            // PageInfo 会自动识别分页数据
            PageInfo<MessageResponseDTO> pageInfo = new PageInfo<>(messageResponseDTOList);

            return Response.<PageInfo<MessageResponseDTO>>builder()
                    .code(ResponseCode.SUCCESS.getCode())
                    .data(pageInfo)
                    .message(ResponseCode.SUCCESS.getMessage())
                    .build();

        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }
}

domain层的接口和实现

public interface IMessageService {
    Integer queryTotalMessageBuyDeviceId(String deviceId);

    List<MessageEntity> queryMessageBuyDeviceIds(List<String> deviceIds);

}
@Service
public class MessageServiceImpl implements IMessageService {

    @Resource
    private IMessageRepository repository;

    @Override
    public Integer queryTotalMessageBuyDeviceId(String deviceId) {



        return repository.queryTotalMessageBuyDeviceId(deviceId);
    }

    
    @Override
    public List<MessageEntity> queryMessageBuyDeviceIds(List<String> deviceIds) {
        MessageReqVO messageReqVO = MessageReqVO.builder()
                .deviceIds(deviceIds)
                .build();
        return repository.queryMessageBuyDeviceIds(messageReqVO);
    }
}

仓储层的实现

@Override
public List<MessageEntity> queryMessageBuyDeviceIds(MessageReqVO messageReqVO) {



    //PageHelper.startPage(1, 5);
    List<SysMessage>  messageList=messageDao.queryMessageBuyDeviceIds(messageReqVO);


    List<MessageEntity> MessageEntityList = messageList.stream().map(message -> {
        MessageEntity messageEntity = new MessageEntity();
        BeanUtils.copyProperties(message, messageEntity);
        return messageEntity;

    }).toList();

    return MessageEntityList;
}


@Override
public Integer queryTotalMessageBuyDeviceId(String deviceId) {
    return messageDao.queryTotalMessageBuyDeviceId(deviceId,null,null);
}


本次复现代码的内容本身不难,但是我对不同解决办法的探索花费了很长的时间,例如我尝试使用aop去实现分页逻辑,虽然我页实现了相关的代码,但是经过思考后发现可能会导致内存溢出等问题。前后很多次不同的尝试花费了很长的时间,最后只能采取这种方法实现。另外需要注意的是,如果PageHelper不起作用推荐更换PageHelpe的版本号可能可以解决问题。

本次复现的代码位于荣先海/xiaozhi-esp32-dd

小智设备链接过程

在复现过程中为了使数据库中有一些有效的数据,这里我选择链接小智AI的硬件设备,试玩一下顺便就可以产生数据了。

修改小智配置

更新小智的固件到最新版,在链接wifi时选择高级设置,把原来的带有http://192.168.5.167:8091/api/device/ota的地址改为java源码启动时日志打印的ota地址即可,这里需要注意的是不推荐使用内网穿透来配置地址,因为这会让设备发送的请求在穿透内网是丢失请求头。注意小智需要和电脑在同一个局域网,不推荐手机热点,这样也会产生请求头丢失的问题。

如果小智在链接wifi是没有高级设置,无法修改ota地址,那请参照xiaozhi-esp32-server-java/docs/FIRMWARE-BUILD.md at main · joey-zhou/xiaozhi-esp32-server-java操作烧录固件

添加小智设备到后台

运行小智java源码,首先点击配置管理,然后点击模型配置,配置对话模型,注意apiurl的配置不要忘记加上v1。大模型的api网上又很多赠送token的。选择一个创建模型即可。创建完模型以后才能在角色配置中添加角色,创建完角色以后才能添加设备。

如果没有硬件设备也是能够玩转后台的,按照上述步骤添加角色,添加完以后点击对话管理,随便输入内容,后台会发送数字到前端,把这些数字输入到设备管理的设备码那一栏即可。

复现03总结

本次复现花费了大量的时间,其中大多数时间花费在链接小智设备和分页查询过滤的设计阶段,代码本身难度不大,在aop上走的弯路过多,最终效果实现了,却发现可能占用太多的内存,于是放弃aop的方案。不过这也使我写代码的能力得到了一点点的提升。复现的路途还有很长,我的技术也还很菜,继续加油吧!

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐