lqw = Wrappers.lambdaQuery();
+ lqw.orderByAsc(HwWeb1::getWebId);
+ lqw.eq(StringUtils.isNotBlank(bo.getWebJson()), HwWeb1::getWebJson, bo.getWebJson());
+ lqw.eq(StringUtils.isNotBlank(bo.getWebJsonString()), HwWeb1::getWebJsonString, bo.getWebJsonString());
+ lqw.eq(bo.getWebCode() != null, HwWeb1::getWebCode, bo.getWebCode());
+ lqw.eq(bo.getDeviceId() != null, HwWeb1::getDeviceId, bo.getDeviceId());
+ lqw.eq(bo.getTypeId() != null, HwWeb1::getTypeId, bo.getTypeId());
+ // @TableLogic 会自动过滤已删除记录,无需手动添加条件
+ lqw.eq(StringUtils.isNotBlank(bo.getWebJsonEnglish()), HwWeb1::getWebJsonEnglish, bo.getWebJsonEnglish());
+ return lqw;
+ }
+
+ /**
+ * 新增haiwei官网json
+ *
+ * @param bo haiwei官网json
+ * @return 是否新增成功
+ */
+ @Override
+ public Boolean insertByBo(HwWeb1Bo bo) {
+ HwWeb1 add = MapstructUtils.convert(bo, HwWeb1.class);
+ // 设置逻辑删除默认值
+ if (add.getIsDelete() == null) {
+ add.setIsDelete("0");
+ }
+ validEntityBeforeSave(add);
+ boolean flag = baseMapper.insert(add) > 0;
+ if (flag) {
+ bo.setWebId(add.getWebId());
+ }
+ return flag;
+ }
+
+ /**
+ * 修改haiwei官网json(版本化替换)
+ *
+ * 业务逻辑说明:
+ * 1. 根据 (webCode, deviceId, typeId) 三元组查询所有已存在的未删除记录
+ * 2. 若存在旧记录,则将这些记录逻辑删除(is_delete = '1')
+ * 3. 插入一条新记录(is_delete = '0')
+ *
+ * 效果:每次更新都会产生新版本记录,旧版本被逻辑删除但保留在数据库中
+ * 注意:与 HwWeb 不同,HwWeb1 按三元组组合保证唯一性
+ *
+ * @param bo haiwei官网json
+ * @return 是否修改成功
+ */
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public Boolean updateByBo(HwWeb1Bo bo) {
+ HwWeb1 update = MapstructUtils.convert(bo, HwWeb1.class);
+ validEntityBeforeSave(update);
+
+ // 第一步:按 (webCode, deviceId, typeId) 三元组查询所有未删除记录(@TableLogic 自动过滤)
+ LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery();
+ queryWrapper.eq(HwWeb1::getWebCode, bo.getWebCode())
+ .eq(bo.getDeviceId() != null, HwWeb1::getDeviceId, bo.getDeviceId())
+ .eq(bo.getTypeId() != null, HwWeb1::getTypeId, bo.getTypeId());
+ List existList = baseMapper.selectList(queryWrapper);
+
+ // 第二步:若存在旧记录,使用 MyBatis-Plus 逻辑删除
+ if (!existList.isEmpty()) {
+ List oldIds = existList.stream().map(HwWeb1::getWebId).toList();
+ // @TableLogic 会自动将 delete 转为 update is_delete = '1'
+ baseMapper.deleteByIds(oldIds);
+ }
+
+ // 第三步:插入新记录,设置为未删除状态
+ update.setWebId(null);
+ update.setIsDelete("0");
+ return baseMapper.insert(update) > 0;
+ }
+
+ /**
+ * 保存前的数据校验
+ */
+ private void validEntityBeforeSave(HwWeb1 entity){
+ //TODO 做一些数据校验,如唯一约束
+ }
+
+ /**
+ * 校验并批量删除haiwei官网json信息(逻辑删除)
+ * 使用 @TableLogic 注解,MyBatis-Plus 会自动将 delete 转为 update is_delete = '1'
+ *
+ * @param ids 待删除的主键集合
+ * @param isValid 是否进行有效性校验
+ * @return 是否删除成功
+ */
+ @Override
+ public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) {
+ if(isValid){
+ //TODO 做一些业务上的校验,判断是否需要校验
+ }
+ // @TableLogic 自动处理逻辑删除
+ return baseMapper.deleteByIds(ids) > 0;
+ }
+
+ /**
+ * 根据页面编码查询haiwei官网json
+ *
+ * @param webCode 页面编码
+ * @return haiwei官网json
+ */
+ @Override
+ public HwWeb1Vo queryByWebCode(Long webCode) {
+ // 查询指定 webCode 的记录(@TableLogic 自动过滤已删除记录)
+ LambdaQueryWrapper lqw = Wrappers.lambdaQuery();
+ lqw.eq(HwWeb1::getWebCode, webCode)
+ .last("LIMIT 1");
+ HwWeb1 entity = baseMapper.selectOne(lqw);
+ return MapstructUtils.convert(entity, HwWeb1Vo.class);
+ }
+
+ /**
+ * 根据 (webCode, deviceId, typeId) 三元组查询唯一记录
+ *
+ * @param bo 包含查询条件的业务对象
+ * @return haiwei官网json
+ */
+ @Override
+ public HwWeb1Vo queryOne(HwWeb1Bo bo) {
+ // 按 (webCode, deviceId, typeId) 三元组查询唯一记录(@TableLogic 自动过滤已删除记录)
+ LambdaQueryWrapper lqw = Wrappers.lambdaQuery();
+ lqw.eq(HwWeb1::getWebCode, bo.getWebCode())
+ .eq(bo.getDeviceId() != null, HwWeb1::getDeviceId, bo.getDeviceId())
+ .eq(bo.getTypeId() != null, HwWeb1::getTypeId, bo.getTypeId())
+ .last("LIMIT 1");
+ HwWeb1 entity = baseMapper.selectOne(lqw);
+ return MapstructUtils.convert(entity, HwWeb1Vo.class);
+ }
+}
diff --git a/ruoyi-modules/hw-web/src/main/java/org/dromara/web/service/impl/HwWebDocumentServiceImpl.java b/ruoyi-modules/hw-web/src/main/java/org/dromara/web/service/impl/HwWebDocumentServiceImpl.java
new file mode 100644
index 0000000..a1ca86a
--- /dev/null
+++ b/ruoyi-modules/hw-web/src/main/java/org/dromara/web/service/impl/HwWebDocumentServiceImpl.java
@@ -0,0 +1,203 @@
+package org.dromara.web.service.impl;
+
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.dromara.web.domain.bo.HwWebDocumentBo;
+import org.dromara.web.domain.vo.HwWebDocumentVo;
+import org.dromara.web.domain.HwWebDocument;
+import org.dromara.web.mapper.HwWebDocumentMapper;
+import org.dromara.web.service.IHwWebDocumentService;
+
+import org.dromara.common.core.exception.ServiceException;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Collection;
+
+/**
+ * hwWebDocumentService业务层处理
+ *
+ * @author zch
+ * @date 2025-11-28
+ */
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class HwWebDocumentServiceImpl implements IHwWebDocumentService {
+
+ private final HwWebDocumentMapper baseMapper;
+
+ /**
+ * 查询hwWebDocument
+ *
+ * @param documentId 主键
+ * @return hwWebDocument
+ */
+ @Override
+ public HwWebDocumentVo queryById(String documentId){
+ return baseMapper.selectCustomHwWebDocumentVoById(documentId);
+ }
+
+ /**
+ * 分页查询hwWebDocument列表
+ *
+ * @param bo 查询条件
+ * @param pageQuery 分页参数
+ * @return hwWebDocument分页列表
+ */
+ @Override
+ public TableDataInfo queryPageList(HwWebDocumentBo bo, PageQuery pageQuery) {
+ LambdaQueryWrapper lqw = buildQueryWrapper(bo);
+ // 使用自定义 Mapper XML + MyBatis-Plus Wrapper 进行分页查询
+ Page result = baseMapper.selectCustomHwWebDocumentVoPage(pageQuery.build(), lqw);
+ return TableDataInfo.build(result);
+ }
+
+ /**
+ * 查询符合条件的hwWebDocument列表
+ *
+ * @param bo 查询条件
+ * @return hwWebDocument列表
+ */
+ @Override
+ public List queryList(HwWebDocumentBo bo) {
+ LambdaQueryWrapper lqw = buildQueryWrapper(bo);
+ // 使用自定义 Mapper XML + MyBatis-Plus Wrapper 查询列表
+ return baseMapper.selectCustomHwWebDocumentVoList(lqw);
+ }
+
+ private LambdaQueryWrapper buildQueryWrapper(HwWebDocumentBo bo) {
+ Map params = bo.getParams();
+ LambdaQueryWrapper lqw = Wrappers.lambdaQuery();
+ lqw.orderByAsc(HwWebDocument::getDocumentId);
+ lqw.eq(StringUtils.isNotBlank(bo.getDocumentAddress()), HwWebDocument::getDocumentAddress, bo.getDocumentAddress());
+ lqw.eq(StringUtils.isNotBlank(bo.getWebCode()), HwWebDocument::getWebCode, bo.getWebCode());
+ lqw.eq(StringUtils.isNotBlank(bo.getSecretKey()), HwWebDocument::getSecretKey, bo.getSecretKey());
+ lqw.eq(StringUtils.isNotBlank(bo.getJson()), HwWebDocument::getJson, bo.getJson());
+ lqw.eq(StringUtils.isNotBlank(bo.getType()), HwWebDocument::getType, bo.getType());
+ // @TableLogic 会自动过滤已删除记录,无需手动添加条件
+ return lqw;
+ }
+
+ /**
+ * 新增hwWebDocument
+ *
+ * @param bo hwWebDocument
+ * @return 是否新增成功
+ */
+ @Override
+ public Boolean insertByBo(HwWebDocumentBo bo) {
+ HwWebDocument add = MapstructUtils.convert(bo, HwWebDocument.class);
+ // 设置逻辑删除默认值
+ if (add.getIsDelete() == null) {
+ add.setIsDelete("0");
+ }
+ validEntityBeforeSave(add);
+ boolean flag = baseMapper.insert(add) > 0;
+ if (flag) {
+ bo.setDocumentId(add.getDocumentId());
+ }
+ return flag;
+ }
+
+ /**
+ * 修改hwWebDocument
+ *
+ * 密钥字段特殊处理逻辑:
+ * - 前端不传或传 null:清空数据库中的密钥(设为 NULL)
+ * - 前端传非空字符串:更新为新的密钥
+ *
+ * 实现方式:将 null 转换为空字符串,配合 MyBatis-Plus 的更新策略
+ *
+ * @param bo hwWebDocument
+ * @return 是否修改成功
+ */
+ @Override
+ public Boolean updateByBo(HwWebDocumentBo bo) {
+ // 特殊处理 secretKey:前端不传或传 null 时,设为空字符串以触发清空密钥操作
+ if (bo.getSecretKey() == null) {
+ bo.setSecretKey("");
+ }
+ HwWebDocument update = MapstructUtils.convert(bo, HwWebDocument.class);
+ validEntityBeforeSave(update);
+ return baseMapper.updateById(update) > 0;
+ }
+
+ /**
+ * 保存前的数据校验
+ */
+ private void validEntityBeforeSave(HwWebDocument entity){
+ //TODO 做一些数据校验,如唯一约束
+ }
+
+ /**
+ * 校验并批量删除hwWebDocument信息(逻辑删除)
+ * 使用 @TableLogic 注解,MyBatis-Plus 会自动将 delete 转为 update is_delete = '1'
+ *
+ * @param ids 待删除的主键集合
+ * @param isValid 是否进行有效性校验
+ * @return 是否删除成功
+ */
+ @Override
+ public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) {
+ if(isValid){
+ //TODO 做一些业务上的校验,判断是否需要校验
+ }
+ // @TableLogic 自动处理逻辑删除
+ return baseMapper.deleteByIds(ids) > 0;
+ }
+
+ /**
+ * 验证密钥并获取文件地址
+ *
+ * 业务逻辑说明:
+ * 1. 根据 documentId 查询文档记录
+ * 2. 若数据库中 secretKey 为空,说明文件无密钥保护,直接返回文件地址
+ * 3. 若数据库中 secretKey 非空,则验证用户提供的密钥:
+ * - 密钥正确:返回文件地址
+ * - 密钥为空:抛出异常"密钥不能为空"
+ * - 密钥错误:抛出异常"密钥错误"
+ *
+ * @param documentId 文件ID
+ * @param providedKey 用户提供的密钥
+ * @return 文件地址
+ */
+ @Override
+ public String verifyAndGetDocumentAddress(String documentId, String providedKey) {
+ // 第一步:查询文件记录
+ HwWebDocumentVo document = queryById(documentId);
+ if (document == null) {
+ throw new ServiceException("文件不存在");
+ }
+
+ String secretKey = document.getSecretKey();
+ String address = document.getDocumentAddress();
+
+ // 第二步:检查文件是否需要密钥保护
+ if (secretKey == null || secretKey.trim().isEmpty()) {
+ // 无密钥保护,直接返回文件地址
+ return address;
+ }
+
+ // 第三步:验证用户提供的密钥
+ String trimmedProvided = providedKey == null ? null : providedKey.trim();
+ if (trimmedProvided == null || trimmedProvided.isEmpty()) {
+ throw new ServiceException("密钥不能为空");
+ }
+
+ // 第四步:比对密钥
+ if (secretKey.trim().equals(trimmedProvided)) {
+ return address;
+ } else {
+ throw new ServiceException("密钥错误");
+ }
+ }
+}
diff --git a/ruoyi-modules/hw-web/src/main/java/org/dromara/web/service/impl/HwWebMenu1ServiceImpl.java b/ruoyi-modules/hw-web/src/main/java/org/dromara/web/service/impl/HwWebMenu1ServiceImpl.java
new file mode 100644
index 0000000..88e9bd1
--- /dev/null
+++ b/ruoyi-modules/hw-web/src/main/java/org/dromara/web/service/impl/HwWebMenu1ServiceImpl.java
@@ -0,0 +1,249 @@
+package org.dromara.web.service.impl;
+
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.dromara.web.domain.bo.HwWebMenu1Bo;
+import org.dromara.web.domain.vo.HwWebMenu1Vo;
+import org.dromara.web.domain.HwWebMenu1;
+import org.dromara.web.mapper.HwWebMenu1Mapper;
+import org.dromara.web.service.IHwWebMenu1Service;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Collection;
+import java.util.stream.Collectors;
+
+/**
+ * HwWebMenu1Service业务层处理
+ *
+ * @author zch
+ * @date 2025-11-28
+ */
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class HwWebMenu1ServiceImpl implements IHwWebMenu1Service {
+
+ private final HwWebMenu1Mapper baseMapper;
+
+ /**
+ * 查询HwWebMenu1
+ *
+ * @param webMenuId 主键
+ * @return HwWebMenu1
+ */
+ @Override
+ public HwWebMenu1Vo queryById(Long webMenuId){
+ return baseMapper.selectCustomHwWebMenu1VoById(webMenuId);
+ }
+
+ /**
+ * 分页查询HwWebMenu1列表
+ *
+ * @param bo 查询条件
+ * @param pageQuery 分页参数
+ * @return HwWebMenu1分页列表
+ */
+ @Override
+ public TableDataInfo queryPageList(HwWebMenu1Bo bo, PageQuery pageQuery) {
+ LambdaQueryWrapper lqw = buildQueryWrapper(bo);
+ // 使用自定义 Mapper XML + MyBatis-Plus Wrapper 进行分页查询
+ Page result = baseMapper.selectCustomHwWebMenu1VoPage(pageQuery.build(), lqw);
+ return TableDataInfo.build(result);
+ }
+
+ /**
+ * 查询符合条件的HwWebMenu1列表
+ *
+ * @param bo 查询条件
+ * @return HwWebMenu1列表
+ */
+ @Override
+ public List queryList(HwWebMenu1Bo bo) {
+ LambdaQueryWrapper lqw = buildQueryWrapper(bo);
+ // 使用自定义 Mapper XML + MyBatis-Plus Wrapper 查询列表
+ return baseMapper.selectCustomHwWebMenu1VoList(lqw);
+ }
+
+ private LambdaQueryWrapper buildQueryWrapper(HwWebMenu1Bo bo) {
+ Map params = bo.getParams();
+ LambdaQueryWrapper lqw = Wrappers.lambdaQuery();
+ lqw.orderByAsc(HwWebMenu1::getWebMenuId);
+ lqw.eq(bo.getParent() != null, HwWebMenu1::getParent, bo.getParent());
+ lqw.eq(StringUtils.isNotBlank(bo.getAncestors()), HwWebMenu1::getAncestors, bo.getAncestors());
+ lqw.eq(StringUtils.isNotBlank(bo.getStatus()), HwWebMenu1::getStatus, bo.getStatus());
+ lqw.like(StringUtils.isNotBlank(bo.getWebMenuName()), HwWebMenu1::getWebMenuName, bo.getWebMenuName());
+ lqw.eq(StringUtils.isNotBlank(bo.getWebMenuPic()), HwWebMenu1::getWebMenuPic, bo.getWebMenuPic());
+ lqw.eq(bo.getWebMenuType() != null, HwWebMenu1::getWebMenuType, bo.getWebMenuType());
+ lqw.eq(StringUtils.isNotBlank(bo.getValue()), HwWebMenu1::getValue, bo.getValue());
+ // @TableLogic 会自动过滤已删除记录,无需手动添加条件
+ lqw.eq(StringUtils.isNotBlank(bo.getWebMenuNameEnglish()), HwWebMenu1::getWebMenuNameEnglish, bo.getWebMenuNameEnglish());
+ return lqw;
+ }
+
+ /**
+ * 新增HwWebMenu1
+ *
+ * @param bo HwWebMenu1
+ * @return 是否新增成功
+ */
+ @Override
+ public Boolean insertByBo(HwWebMenu1Bo bo) {
+ HwWebMenu1 add = MapstructUtils.convert(bo, HwWebMenu1.class);
+ // 设置逻辑删除默认值
+ if (add.getIsDelete() == null) {
+ add.setIsDelete("0");
+ }
+ validEntityBeforeSave(add);
+ boolean flag = baseMapper.insert(add) > 0;
+ if (flag) {
+ bo.setWebMenuId(add.getWebMenuId());
+ }
+ return flag;
+ }
+
+ /**
+ * 修改HwWebMenu1
+ *
+ * @param bo HwWebMenu1
+ * @return 是否修改成功
+ */
+ @Override
+ public Boolean updateByBo(HwWebMenu1Bo bo) {
+ HwWebMenu1 update = MapstructUtils.convert(bo, HwWebMenu1.class);
+ validEntityBeforeSave(update);
+ return baseMapper.updateById(update) > 0;
+ }
+
+ /**
+ * 保存前的数据校验
+ */
+ private void validEntityBeforeSave(HwWebMenu1 entity){
+ //TODO 做一些数据校验,如唯一约束
+ }
+
+ /**
+ * 校验并批量删除HwWebMenu1信息(逻辑删除)
+ * 使用 @TableLogic 注解,MyBatis-Plus 会自动将 delete 转为 update is_delete = '1'
+ *
+ * @param ids 待删除的主键集合
+ * @param isValid 是否进行有效性校验
+ * @return 是否删除成功
+ */
+ @Override
+ public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) {
+ if(isValid){
+ //TODO 做一些业务上的校验,判断是否需要校验
+ }
+ // @TableLogic 自动处理逻辑删除
+ return baseMapper.deleteByIds(ids) > 0;
+ }
+
+ /**
+ * 获取菜单树列表
+ *
+ * 业务逻辑说明:
+ * 1. 先查询出平铺的菜单列表(所有未删除记录)
+ * 2. 然后根据 parent 字段递归构建树形结构
+ * 3. 前端可直接使用返回的带 children 的树形数据
+ *
+ * @param bo 查询条件
+ * @return 树形结构菜单列表
+ */
+ @Override
+ public List selectMenuTree(HwWebMenu1Bo bo) {
+ // 第一步:查询平铺的菜单列表
+ List menuList = queryList(bo);
+ // 第二步:构建树形结构
+ return buildWebMenuTree(menuList);
+ }
+
+ /**
+ * 构建前端所需要树结构(根据传入的平铺菜单列表构造树)
+ *
+ * 树构建算法说明:
+ * 1. 收集所有菜单的 webMenuId 到列表 tempList
+ * 2. 遍历每个菜单,判断是否为根节点:
+ * - parent 为 null 或 0:是根节点
+ * - parent 不在 tempList 中:父节点不在当前集合中,视为根节点
+ * 3. 对每个根节点递归设置子节点
+ *
+ * @param menus 菜单列表
+ * @return 树结构列表
+ */
+ private List buildWebMenuTree(List menus) {
+ List returnList = new ArrayList<>();
+ // 第一步:收集所有菜单ID
+ List tempList = menus.stream()
+ .map(HwWebMenu1Vo::getWebMenuId)
+ .collect(Collectors.toList());
+
+ // 第二步:找出所有根节点并递归构建子树
+ for (HwWebMenu1Vo menu : menus) {
+ // 判断是否为顶级节点(parent为null、0或不在当前列表中)
+ if (menu.getParent() == null || menu.getParent() == 0L || !tempList.contains(menu.getParent())) {
+ recursionFn(menus, menu);
+ returnList.add(menu);
+ }
+ }
+ // 如果没有找到顶级节点,直接返回原列表
+ if (returnList.isEmpty()) {
+ returnList = menus;
+ }
+ return returnList;
+ }
+
+ /**
+ * 递归设置子节点
+ *
+ * @param list 所有菜单列表
+ * @param t 当前节点
+ */
+ private void recursionFn(List list, HwWebMenu1Vo t) {
+ // 得到子节点列表
+ List childList = getChildList(list, t);
+ t.setChildren(childList);
+ for (HwWebMenu1Vo tChild : childList) {
+ if (hasChild(list, tChild)) {
+ recursionFn(list, tChild);
+ }
+ }
+ }
+
+ /**
+ * 得到子节点列表
+ *
+ * @param list 所有菜单列表
+ * @param t 当前节点
+ * @return 子节点列表
+ */
+ private List getChildList(List list, HwWebMenu1Vo t) {
+ List tlist = new ArrayList<>();
+ for (HwWebMenu1Vo n : list) {
+ if (n.getParent() != null && n.getParent().longValue() == t.getWebMenuId().longValue()) {
+ tlist.add(n);
+ }
+ }
+ return tlist;
+ }
+
+ /**
+ * 判断是否有子节点
+ *
+ * @param list 所有菜单列表
+ * @param t 当前节点
+ * @return 是否有子节点
+ */
+ private boolean hasChild(List list, HwWebMenu1Vo t) {
+ return !getChildList(list, t).isEmpty();
+ }
+}
diff --git a/ruoyi-modules/hw-web/src/main/java/org/dromara/web/service/impl/HwWebMenuServiceImpl.java b/ruoyi-modules/hw-web/src/main/java/org/dromara/web/service/impl/HwWebMenuServiceImpl.java
new file mode 100644
index 0000000..dd936cf
--- /dev/null
+++ b/ruoyi-modules/hw-web/src/main/java/org/dromara/web/service/impl/HwWebMenuServiceImpl.java
@@ -0,0 +1,248 @@
+package org.dromara.web.service.impl;
+
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.dromara.web.domain.bo.HwWebMenuBo;
+import org.dromara.web.domain.vo.HwWebMenuVo;
+import org.dromara.web.domain.HwWebMenu;
+import org.dromara.web.mapper.HwWebMenuMapper;
+import org.dromara.web.service.IHwWebMenuService;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Collection;
+import java.util.stream.Collectors;
+
+/**
+ * HwWebMenuService业务层处理
+ *
+ * @author zch
+ * @date 2025-11-28
+ */
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class HwWebMenuServiceImpl implements IHwWebMenuService {
+
+ private final HwWebMenuMapper baseMapper;
+
+ /**
+ * 查询HwWebMenu
+ *
+ * @param webMenuId 主键
+ * @return HwWebMenu
+ */
+ @Override
+ public HwWebMenuVo queryById(Long webMenuId){
+ return baseMapper.selectCustomHwWebMenuVoById(webMenuId);
+ }
+
+ /**
+ * 分页查询HwWebMenu列表
+ *
+ * @param bo 查询条件
+ * @param pageQuery 分页参数
+ * @return HwWebMenu分页列表
+ */
+ @Override
+ public TableDataInfo queryPageList(HwWebMenuBo bo, PageQuery pageQuery) {
+ LambdaQueryWrapper lqw = buildQueryWrapper(bo);
+ // 使用自定义 Mapper XML + MyBatis-Plus Wrapper 进行分页查询
+ Page result = baseMapper.selectCustomHwWebMenuVoPage(pageQuery.build(), lqw);
+ return TableDataInfo.build(result);
+ }
+
+ /**
+ * 查询符合条件的HwWebMenu列表
+ *
+ * @param bo 查询条件
+ * @return HwWebMenu列表
+ */
+ @Override
+ public List queryList(HwWebMenuBo bo) {
+ LambdaQueryWrapper lqw = buildQueryWrapper(bo);
+ // 使用自定义 Mapper XML + MyBatis-Plus Wrapper 查询列表
+ return baseMapper.selectCustomHwWebMenuVoList(lqw);
+ }
+
+ private LambdaQueryWrapper buildQueryWrapper(HwWebMenuBo bo) {
+ Map params = bo.getParams();
+ LambdaQueryWrapper lqw = Wrappers.lambdaQuery();
+ lqw.orderByAsc(HwWebMenu::getWebMenuId);
+ lqw.eq(bo.getParent() != null, HwWebMenu::getParent, bo.getParent());
+ lqw.eq(StringUtils.isNotBlank(bo.getAncestors()), HwWebMenu::getAncestors, bo.getAncestors());
+ lqw.eq(StringUtils.isNotBlank(bo.getStatus()), HwWebMenu::getStatus, bo.getStatus());
+ lqw.like(StringUtils.isNotBlank(bo.getWebMenuName()), HwWebMenu::getWebMenuName, bo.getWebMenuName());
+ lqw.eq(StringUtils.isNotBlank(bo.getWebMenuPic()), HwWebMenu::getWebMenuPic, bo.getWebMenuPic());
+ lqw.eq(bo.getWebMenuType() != null, HwWebMenu::getWebMenuType, bo.getWebMenuType());
+ // @TableLogic 会自动过滤已删除记录,无需手动添加条件
+ lqw.eq(StringUtils.isNotBlank(bo.getWebMenuNameEnglish()), HwWebMenu::getWebMenuNameEnglish, bo.getWebMenuNameEnglish());
+ return lqw;
+ }
+
+ /**
+ * 新增HwWebMenu
+ *
+ * @param bo HwWebMenu
+ * @return 是否新增成功
+ */
+ @Override
+ public Boolean insertByBo(HwWebMenuBo bo) {
+ HwWebMenu add = MapstructUtils.convert(bo, HwWebMenu.class);
+ // 设置逻辑删除默认值
+ if (add.getIsDelete() == null) {
+ add.setIsDelete("0");
+ }
+ validEntityBeforeSave(add);
+ boolean flag = baseMapper.insert(add) > 0;
+ if (flag) {
+ bo.setWebMenuId(add.getWebMenuId());
+ }
+ return flag;
+ }
+
+ /**
+ * 修改HwWebMenu
+ *
+ * @param bo HwWebMenu
+ * @return 是否修改成功
+ */
+ @Override
+ public Boolean updateByBo(HwWebMenuBo bo) {
+ HwWebMenu update = MapstructUtils.convert(bo, HwWebMenu.class);
+ validEntityBeforeSave(update);
+ return baseMapper.updateById(update) > 0;
+ }
+
+ /**
+ * 保存前的数据校验
+ */
+ private void validEntityBeforeSave(HwWebMenu entity){
+ //TODO 做一些数据校验,如唯一约束
+ }
+
+ /**
+ * 校验并批量删除HwWebMenu信息(逻辑删除)
+ * 使用 @TableLogic 注解,MyBatis-Plus 会自动将 delete 转为 update is_delete = '1'
+ *
+ * @param ids 待删除的主键集合
+ * @param isValid 是否进行有效性校验
+ * @return 是否删除成功
+ */
+ @Override
+ public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) {
+ if(isValid){
+ //TODO 做一些业务上的校验,判断是否需要校验
+ }
+ // @TableLogic 自动处理逻辑删除
+ return baseMapper.deleteByIds(ids) > 0;
+ }
+
+ /**
+ * 获取菜单树列表
+ *
+ * 业务逻辑说明:
+ * 1. 先查询出平铺的菜单列表(所有未删除记录)
+ * 2. 然后根据 parent 字段递归构建树形结构
+ * 3. 前端可直接使用返回的带 children 的树形数据
+ *
+ * @param bo 查询条件
+ * @return 树形结构菜单列表
+ */
+ @Override
+ public List selectMenuTree(HwWebMenuBo bo) {
+ // 第一步:查询平铺的菜单列表
+ List menuList = queryList(bo);
+ // 第二步:构建树形结构
+ return buildWebMenuTree(menuList);
+ }
+
+ /**
+ * 构建前端所需要树结构(根据传入的平铺菜单列表构造树)
+ *
+ * 树构建算法说明:
+ * 1. 收集所有菜单的 webMenuId 到列表 tempList
+ * 2. 遍历每个菜单,判断是否为根节点:
+ * - parent 为 null 或 0:是根节点
+ * - parent 不在 tempList 中:父节点不在当前集合中,视为根节点
+ * 3. 对每个根节点递归设置子节点
+ *
+ * @param menus 菜单列表
+ * @return 树结构列表
+ */
+ private List buildWebMenuTree(List menus) {
+ List returnList = new ArrayList<>();
+ // 第一步:收集所有菜单ID
+ List tempList = menus.stream()
+ .map(HwWebMenuVo::getWebMenuId)
+ .collect(Collectors.toList());
+
+ // 第二步:找出所有根节点并递归构建子树
+ for (HwWebMenuVo menu : menus) {
+ // 判断是否为顶级节点(parent为null、0或不在当前列表中)
+ if (menu.getParent() == null || menu.getParent() == 0L || !tempList.contains(menu.getParent())) {
+ recursionFn(menus, menu);
+ returnList.add(menu);
+ }
+ }
+ // 如果没有找到顶级节点,直接返回原列表
+ if (returnList.isEmpty()) {
+ returnList = menus;
+ }
+ return returnList;
+ }
+
+ /**
+ * 递归设置子节点
+ *
+ * @param list 所有菜单列表
+ * @param t 当前节点
+ */
+ private void recursionFn(List list, HwWebMenuVo t) {
+ // 得到子节点列表
+ List childList = getChildList(list, t);
+ t.setChildren(childList);
+ for (HwWebMenuVo tChild : childList) {
+ if (hasChild(list, tChild)) {
+ recursionFn(list, tChild);
+ }
+ }
+ }
+
+ /**
+ * 得到子节点列表
+ *
+ * @param list 所有菜单列表
+ * @param t 当前节点
+ * @return 子节点列表
+ */
+ private List getChildList(List list, HwWebMenuVo t) {
+ List tlist = new ArrayList<>();
+ for (HwWebMenuVo n : list) {
+ if (n.getParent() != null && n.getParent().longValue() == t.getWebMenuId().longValue()) {
+ tlist.add(n);
+ }
+ }
+ return tlist;
+ }
+
+ /**
+ * 判断是否有子节点
+ *
+ * @param list 所有菜单列表
+ * @param t 当前节点
+ * @return 是否有子节点
+ */
+ private boolean hasChild(List list, HwWebMenuVo t) {
+ return !getChildList(list, t).isEmpty();
+ }
+}
diff --git a/ruoyi-modules/hw-web/src/main/java/org/dromara/web/service/impl/HwWebServiceImpl.java b/ruoyi-modules/hw-web/src/main/java/org/dromara/web/service/impl/HwWebServiceImpl.java
new file mode 100644
index 0000000..49861da
--- /dev/null
+++ b/ruoyi-modules/hw-web/src/main/java/org/dromara/web/service/impl/HwWebServiceImpl.java
@@ -0,0 +1,193 @@
+package org.dromara.web.service.impl;
+
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.dromara.web.domain.bo.HwWebBo;
+import org.dromara.web.domain.vo.HwWebVo;
+import org.dromara.web.domain.HwWeb;
+import org.dromara.web.mapper.HwWebMapper;
+import org.dromara.web.service.IHwWebService;
+
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Collection;
+
+/**
+ * haiwei官网jsonService业务层处理
+ *
+ * @author zch
+ * @date 2025-11-28
+ */
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class HwWebServiceImpl implements IHwWebService {
+
+ private final HwWebMapper baseMapper;
+
+ /**
+ * 查询haiwei官网json
+ *
+ * @param webId 主键
+ * @return haiwei官网json
+ */
+ @Override
+ public HwWebVo queryById(Long webId){
+ return baseMapper.selectCustomHwWebVoById(webId);
+ }
+
+ /**
+ * 分页查询haiwei官网json列表
+ *
+ * @param bo 查询条件
+ * @param pageQuery 分页参数
+ * @return haiwei官网json分页列表
+ */
+ @Override
+ public TableDataInfo queryPageList(HwWebBo bo, PageQuery pageQuery) {
+ LambdaQueryWrapper lqw = buildQueryWrapper(bo);
+ // 使用自定义 Mapper XML + MyBatis-Plus Wrapper 进行分页查询
+ Page result = baseMapper.selectCustomHwWebVoPage(pageQuery.build(), lqw);
+ return TableDataInfo.build(result);
+ }
+
+ /**
+ * 查询符合条件的haiwei官网json列表
+ *
+ * @param bo 查询条件
+ * @return haiwei官网json列表
+ */
+ @Override
+ public List queryList(HwWebBo bo) {
+ LambdaQueryWrapper lqw = buildQueryWrapper(bo);
+ // 使用自定义 Mapper XML + MyBatis-Plus Wrapper 查询列表
+ return baseMapper.selectCustomHwWebVoList(lqw);
+ }
+
+ /**
+ * 构建查询条件包装器
+ * 注意:使用 @TableLogic 后,MyBatis-Plus 会自动过滤已删除记录
+ *
+ * @param bo 查询条件
+ * @return 查询条件包装器
+ */
+ private LambdaQueryWrapper buildQueryWrapper(HwWebBo bo) {
+ Map params = bo.getParams();
+ LambdaQueryWrapper lqw = Wrappers.lambdaQuery();
+ lqw.orderByAsc(HwWeb::getWebId);
+ lqw.eq(StringUtils.isNotBlank(bo.getWebJson()), HwWeb::getWebJson, bo.getWebJson());
+ lqw.eq(StringUtils.isNotBlank(bo.getWebJsonString()), HwWeb::getWebJsonString, bo.getWebJsonString());
+ lqw.eq(bo.getWebCode() != null, HwWeb::getWebCode, bo.getWebCode());
+ // @TableLogic 会自动过滤已删除记录,无需手动添加条件
+ lqw.eq(StringUtils.isNotBlank(bo.getWebJsonEnglish()), HwWeb::getWebJsonEnglish, bo.getWebJsonEnglish());
+ return lqw;
+ }
+
+ /**
+ * 新增haiwei官网json
+ *
+ * @param bo haiwei官网json
+ * @return 是否新增成功
+ */
+ @Override
+ public Boolean insertByBo(HwWebBo bo) {
+ HwWeb add = MapstructUtils.convert(bo, HwWeb.class);
+ // 设置逻辑删除默认值
+ if (add.getIsDelete() == null) {
+ add.setIsDelete("0");
+ }
+ validEntityBeforeSave(add);
+ boolean flag = baseMapper.insert(add) > 0;
+ if (flag) {
+ bo.setWebId(add.getWebId());
+ }
+ return flag;
+ }
+
+ /**
+ * 修改haiwei官网json(版本化替换)
+ *
+ * 业务逻辑说明:
+ * 1. 根据 webCode 查询所有已存在的未删除记录
+ * 2. 若存在旧记录,则将这些记录逻辑删除(is_delete = '1')
+ * 3. 插入一条新记录(is_delete = '0')
+ *
+ * 效果:每次更新都会产生新版本记录,旧版本被逻辑删除但保留在数据库中
+ *
+ * @param bo haiwei官网json
+ * @return 是否修改成功
+ */
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public Boolean updateByBo(HwWebBo bo) {
+ HwWeb update = MapstructUtils.convert(bo, HwWeb.class);
+ validEntityBeforeSave(update);
+
+ // 第一步:查询同一 webCode 的所有未删除记录(@TableLogic 自动过滤)
+ LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery();
+ queryWrapper.eq(HwWeb::getWebCode, bo.getWebCode());
+ List existList = baseMapper.selectList(queryWrapper);
+
+ // 第二步:若存在旧记录,使用 MyBatis-Plus 逻辑删除
+ if (!existList.isEmpty()) {
+ List oldIds = existList.stream().map(HwWeb::getWebId).toList();
+ // @TableLogic 会自动将 delete 转为 update is_delete = '1'
+ baseMapper.deleteByIds(oldIds);
+ }
+
+ // 第三步:插入新记录,设置为未删除状态
+ update.setWebId(null);
+ update.setIsDelete("0");
+ return baseMapper.insert(update) > 0;
+ }
+
+ /**
+ * 保存前的数据校验
+ */
+ private void validEntityBeforeSave(HwWeb entity){
+ //TODO 做一些数据校验,如唯一约束
+ }
+
+ /**
+ * 校验并批量删除haiwei官网json信息(逻辑删除)
+ * 使用 @TableLogic 注解,MyBatis-Plus 会自动将 delete 转为 update is_delete = '1'
+ *
+ * @param ids 待删除的主键集合
+ * @param isValid 是否进行有效性校验
+ * @return 是否删除成功
+ */
+ @Override
+ public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) {
+ if(isValid){
+ //TODO 做一些业务上的校验,判断是否需要校验
+ }
+ // @TableLogic 自动处理逻辑删除
+ return baseMapper.deleteByIds(ids) > 0;
+ }
+
+ /**
+ * 根据页面编码查询haiwei官网json
+ *
+ * @param webCode 页面编码
+ * @return haiwei官网json
+ */
+ @Override
+ public HwWebVo queryByWebCode(Long webCode) {
+ // 查询指定 webCode 的记录(@TableLogic 自动过滤已删除记录)
+ LambdaQueryWrapper lqw = Wrappers.lambdaQuery();
+ lqw.eq(HwWeb::getWebCode, webCode)
+ .last("LIMIT 1");
+ HwWeb entity = baseMapper.selectOne(lqw);
+ return MapstructUtils.convert(entity, HwWebVo.class);
+ }
+}
diff --git a/ruoyi-modules/hw-web/src/main/resources/mapper/web/HwWeb1Mapper.xml b/ruoyi-modules/hw-web/src/main/resources/mapper/web/HwWeb1Mapper.xml
new file mode 100644
index 0000000..f1a5220
--- /dev/null
+++ b/ruoyi-modules/hw-web/src/main/resources/mapper/web/HwWeb1Mapper.xml
@@ -0,0 +1,176 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ insert into hw_web1(
+ web_json,
+
+ web_json_string,
+
+ web_code,
+
+ device_id,
+
+ file_address,
+
+ secret_ket,
+
+ typeId,
+
+ is_delete,
+
+ update_time,
+
+ create_time,
+
+ web_json_english
+
+ )
+ values
+
+ (
+ #{item.webJson},
+
+ #{item.webJsonString},
+
+ #{item.webCode},
+
+ #{item.deviceId},
+
+ #{item.fileAddress},
+
+ #{item.secretKet},
+
+ #{item.typeid},
+
+ #{item.isDelete},
+
+ #{item.updateTime},
+
+ #{item.createTime},
+
+ #{item.webJsonEnglish}
+
+ )
+
+
+
+
+
+
+ update hw_web1
+
+
+ web_json = #{item.webJson},
+
+
+ web_json_string = #{item.webJsonString},
+
+
+ web_code = #{item.webCode},
+
+
+ device_id = #{item.deviceId},
+
+
+ file_address = #{item.fileAddress},
+
+
+ secret_ket = #{item.secretKet},
+
+
+ typeId = #{item.typeid},
+
+
+ is_delete = #{item.isDelete},
+
+
+ update_time = #{item.updateTime},
+
+
+ create_time = #{item.createTime},
+
+
+ web_json_english = #{item.webJsonEnglish}
+
+
+ where web_id = #{item.webId}
+
+
+
+
+
+ delete from hw_web1
+
+ ${ew.customSqlSegment}
+
+
+
+
+
+ delete from hw_web1
+ where web_id in
+
+ #{id}
+
+
+
+
+
+
+
+
diff --git a/ruoyi-modules/hw-web/src/main/resources/mapper/web/HwWebDocumentMapper.xml b/ruoyi-modules/hw-web/src/main/resources/mapper/web/HwWebDocumentMapper.xml
new file mode 100644
index 0000000..0ebb74e
--- /dev/null
+++ b/ruoyi-modules/hw-web/src/main/resources/mapper/web/HwWebDocumentMapper.xml
@@ -0,0 +1,166 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ insert into hw_web_document(
+ document_id,
+
+ tenant_id,
+
+ document_address,
+
+ create_time,
+
+ web_code,
+
+ secretKey,
+
+ json,
+
+ type,
+
+ is_delete,
+
+ update_time
+
+ )
+ values
+
+ (
+ #{item.documentId},
+
+ #{item.tenantId},
+
+ #{item.documentAddress},
+
+ #{item.createTime},
+
+ #{item.webCode},
+
+ #{item.secretkey},
+
+ #{item.json},
+
+ #{item.type},
+
+ #{item.isDelete},
+
+ #{item.updateTime}
+
+ )
+
+
+
+
+
+
+ update hw_web_document
+
+
+ tenant_id = #{item.tenantId},
+
+
+ document_address = #{item.documentAddress},
+
+
+ create_time = #{item.createTime},
+
+
+ web_code = #{item.webCode},
+
+
+ secretKey = #{item.secretkey},
+
+
+ json = #{item.json},
+
+
+ type = #{item.type},
+
+
+ is_delete = #{item.isDelete},
+
+
+ update_time = #{item.updateTime}
+
+
+ where document_id = #{item.documentId}
+
+
+
+
+
+ delete from hw_web_document
+
+ ${ew.customSqlSegment}
+
+
+
+
+
+ delete from hw_web_document
+ where document_id in
+
+ #{id}
+
+
+
+
+
+
+
+
diff --git a/ruoyi-modules/hw-web/src/main/resources/mapper/web/HwWebMapper.xml b/ruoyi-modules/hw-web/src/main/resources/mapper/web/HwWebMapper.xml
new file mode 100644
index 0000000..8d9eee7
--- /dev/null
+++ b/ruoyi-modules/hw-web/src/main/resources/mapper/web/HwWebMapper.xml
@@ -0,0 +1,148 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ insert into hw_web(
+ web_json,
+
+ web_json_string,
+
+ web_code,
+
+ is_delete,
+
+ update_time,
+
+ create_time,
+
+ web_json_english
+
+ )
+ values
+
+ (
+ #{item.webJson},
+
+ #{item.webJsonString},
+
+ #{item.webCode},
+
+ #{item.isDelete},
+
+ #{item.updateTime},
+
+ #{item.createTime},
+
+ #{item.webJsonEnglish}
+
+ )
+
+
+
+
+
+
+ update hw_web
+
+
+ web_json = #{item.webJson},
+
+
+ web_json_string = #{item.webJsonString},
+
+
+ web_code = #{item.webCode},
+
+
+ is_delete = #{item.isDelete},
+
+
+ update_time = #{item.updateTime},
+
+
+ create_time = #{item.createTime},
+
+
+ web_json_english = #{item.webJsonEnglish}
+
+
+ where web_id = #{item.webId}
+
+
+
+
+
+ delete from hw_web
+
+ ${ew.customSqlSegment}
+
+
+
+
+
+ delete from hw_web
+ where web_id in
+
+ #{id}
+
+
+
+
+
+
+
+
diff --git a/ruoyi-modules/hw-web/src/main/resources/mapper/web/HwWebMenu1Mapper.xml b/ruoyi-modules/hw-web/src/main/resources/mapper/web/HwWebMenu1Mapper.xml
new file mode 100644
index 0000000..ebd5f13
--- /dev/null
+++ b/ruoyi-modules/hw-web/src/main/resources/mapper/web/HwWebMenu1Mapper.xml
@@ -0,0 +1,183 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ruoyi-modules/hw-web/src/main/resources/mapper/web/HwWebMenuMapper.xml b/ruoyi-modules/hw-web/src/main/resources/mapper/web/HwWebMenuMapper.xml
new file mode 100644
index 0000000..e31e6d9
--- /dev/null
+++ b/ruoyi-modules/hw-web/src/main/resources/mapper/web/HwWebMenuMapper.xml
@@ -0,0 +1,176 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ruoyi-modules/hw-web/web.md b/ruoyi-modules/hw-web/web.md
new file mode 100644
index 0000000..8009115
--- /dev/null
+++ b/ruoyi-modules/hw-web/web.md
@@ -0,0 +1,2235 @@
+# hw-portal 官网模块说明(HwWeb / HwWeb1 / HwWebDocument / HwWebMenu / HwWebMenu1)
+
+> 仅关注与 `HwWeb`、`HwWeb1`、`HwWebDocument`、`HwWebMenu`、`HwWebMenu1` 相关的代码逻辑,方便 AI 与技术人员统一阅读和分析。
+
+## 0.后端代码注意事项
+- hw-portal是微服务旧项目(springboot2+mybatis)的某个模块直接复制过来,现在是springboot3+MybatisPlus的单体新项目,需要将旧项目hw-portal模块的HwWeb、HwWeb1、HwWebDocument、HwWebMenu、HwWebMenu1的后端代码逻辑全部迁移到新项目的rfid-middleware\ruoyi-modules\hw-web模块中
+- 注意只关注HwWeb、HwWeb1、HwWebDocument、HwWebMenu、HwWebMenu1!
+- 要求实体类的字段类型都与旧项目一致!
+- 注意前端的判断依据大多都是code!
+- 接口必须与旧项目完全一致,以方便前端直接调用!
+- 注意旧项目接口的权限控制已经全部注释掉了!
+- 要求代码逻辑不变,尽量贴合现在新项目的springboot3+MybatisPlus!
+- 代码的实现方式可以考虑MybatisPlus自带方法,比如说增删改查或者是逻辑删除,但是业务逻辑必须要一致!
+- 数据库表结构中is_delete字段0表示未删除,1表示已删除
+- 注意现在新项目的文件服务也已经与旧项目完全不一样了,需要匹配现有新项目!
+- 数据库表结构与BaseEntity或者TenantEntity不一致,不应该继承,将相关字段填入在自己实体类中即可,注意@TableField(fill = FieldFill.INSERT)
+
+---
+
+## 1. 模块总览
+
+- **页面 JSON 配置**
+ - 表:`hw_web`,实体:`HwWeb`
+ - 表:`hw_web1`,实体:`HwWeb1`
+ - 作用:存储官网页面的 JSON 配置(含中英文),`hw_web1` 在 `hw_web` 基础上增加了 `deviceId`、`typeId` 维度,实现「页面编码 + 设备 + 类型」组合唯一。
+
+- **官网菜单**
+ - 表:`hw_web_menu`,实体:`HwWebMenu`
+ - 表:`hw_web_menu1`,实体:`HwWebMenu1`
+ - 作用:存储官网菜单树结构,支持多层级父子关系;其中 `HwWebMenu1` 相比 `HwWebMenu` 额外包含 `valuel` 字段(数据库列名为 `value`),用于绑定前端路由值或业务编码。
+
+- **资料文件(带密钥保护)**
+ - 表:`hw_web_document`,实体:`HwWebDocument`
+ - 作用:存储资料文件的访问地址、所属页面编码、类型等,同时支持可选密钥保护(有密钥就必须验证才能拿到真实地址)。
+
+- **软删除统一约定**
+ - 所有上述表都使用 `is_delete` 字段做逻辑删除:
+ - `'0'`:未删除
+ - `'1'`:已删除
+ - Mapper XML 中所有查询均默认附带 `and is_delete = '0'` 条件。
+
+---
+
+## 2. 页面 JSON:HwWeb / HwWeb1
+
+### 2.1 实体结构
+
+- **`HwWeb`(表:`hw_web`)**
+ - `webId`:主键 ID
+ - `webJson`:JSON 内容
+ - `webJsonString`:JSON 的字符串表示(便于展示或简单查询)
+ - `webCode`:页面编码,作为业务主键
+ - `isDelete`:逻辑删除标志
+ - `webJsonEnglish`:英文 JSON 字符串
+
+- **`HwWeb1`(表:`hw_web1`)**
+ - 在 `HwWeb` 的基础上新增:
+ - `deviceId`:设备 ID
+ - `typeId`:类型 ID
+ - 唯一约束在服务层通过 `(webCode, deviceId, typeId)` 组合保证。
+
+**字段类型与数据库映射:**
+
+- `HwWeb`:
+
+| 字段名 | Java 类型 | 数据库列名 | 说明 |
+|--------|-----------|------------|------|
+| `webId` | `Long` | `web_id` | 主键 ID |
+| `webJson` | `String` | `web_json` | 页面 JSON 内容 |
+| `webJsonString` | `String` | `web_json_string` | JSON 的字符串形式 |
+| `webCode` | `Long` | `web_code` | 页面编码,业务主键 |
+| `isDelete` | `String` | `is_delete` | 逻辑删除标志,`0` 未删,`1` 已删 |
+| `webJsonEnglish` | `String` | `web_json_english` | 英文 JSON 字符串 |
+
+- `HwWeb1`:
+
+| 字段名 | Java 类型 | 数据库列名 | 说明 |
+|--------|-----------|------------|------|
+| `webId` | `Long` | `web_id` | 主键 ID |
+| `webJson` | `String` | `web_json` | 页面 JSON 内容 |
+| `webJsonString` | `String` | `web_json_string` | JSON 的字符串形式 |
+| `webCode` | `Long` | `web_code` | 页面编码,业务主键 |
+| `deviceId` | `Long` | `device_id` | 设备 ID,用于区分终端 |
+| `typeId` | `Long` | `typeId` | 类型 ID,用于区分业务/场景 |
+| `isDelete` | `String` | `is_delete` | 逻辑删除标志,`0` 未删,`1` 已删 |
+| `webJsonEnglish` | `String` | `web_json_english` | 英文 JSON 字符串 |
+
+### 2.2 Mapper & XML 行为
+
+#### 2.2.1 `HwWebMapper` + `HwWebMapper.xml`
+
+- **查询列表**:`selectHwWebList(HwWeb hwWeb)`
+ - 基础 SQL:`select web_id, web_json, ... from hw_web`
+ - 统一条件:`and is_delete = '0'`
+ - 支持按 `webId`、`webJson`、`webJsonString`、`webCode`、`webJsonEnglish` 等过滤。
+
+- **单条查询**:`selectHwWebByWebcode(Long webCode)`
+ - 在基础 SQL 上追加:`where is_delete = '0' and web_code = #{webCode}`
+
+- **插入**:`insertHwWeb(HwWeb hwWeb)`
+ - 按字段非空插入,并默认 `is_delete = '0'`
+
+- **更新**:`updateHwWeb(HwWeb hwWeb)`
+ - 只允许更新 `web_json`、`web_json_string`、`web_json_english`,不更新 `web_code` 和主键。
+ - 更新条件为:`where web_code = #{webCode}`。
+
+- **删除 / 批量删除**
+ - `deleteHwWebByWebId(Long webId)`:`update hw_web set is_delete = '1' where web_id = #{webId}`
+ - `deleteHwWebByWebIds(Long[] webIds)`:`update ... where web_id in (...)`,同样为逻辑删除。
+
+#### 2.2.2 `HwWebMapper1` + `HwWebMapper1.xml`
+
+- 行为与 `HwWebMapper` 类似,差异:
+ - 额外字段 `device_id`、`typeId`、`web_json_english`。
+ - `selectHwWebOne(HwWeb1 hwWeb1)`:按 `web_code + device_id + typeId` 精确查询一条记录。
+ - `updateHwWeb(HwWeb1 hwWeb1)` 的 where 条件为:
+ - `where web_code = #{webCode} and device_id = #{deviceId} and typeId = #{typeId}`
+
+### 2.3 Service 接口与实现
+
+#### 2.3.1 接口:`IHwWebService` / `IHwWebService1`
+
+- 统一约定的方法:
+ - `selectHwWebByWebcode(Long webCode)`
+ - `selectHwWebList(...)`
+ - `insertHwWeb(...)`
+ - `updateHwWeb(...)`
+ - `deleteHwWebByWebIds(Long[] webIds)`
+ - `deleteHwWebByWebId(Long webId)`
+ - `IHwWebService1` 额外提供:`selectHwWebOne(HwWeb1 hwWeb1)`
+
+#### 2.3.2 实现:`HwWebServiceImpl`(基于 `hw_web`)
+
+- **查询**
+ - `selectHwWebByWebcode`:直接委托 Mapper,对 `webCode` 进行精确查询。
+ - `selectHwWebList`:委托 Mapper,按入参条件筛选列表。
+
+- **新增**
+ - `insertHwWeb`:调用 Mapper 插入,未做额外业务校验。
+
+- **更新(逻辑:版本化替换)**
+ - `updateHwWeb(HwWeb hwWeb)`:
+ - 构造 `codeWeb`,只设置 `webCode`。
+ - 调用 `selectHwWebList(codeWeb)` 查出该编码下所有未删除记录。
+ - 若存在:
+ - 提取这些记录的 `webId`,组装为数组。
+ - 调用 `deleteHwWebByWebIds` 对这些旧记录做「逻辑删除」。
+ - 为新纪录显式设置 `isDelete = "0"`。
+ - 调用 `insertHwWeb(hwWeb)` 插入一条**新的**记录(而不是更新原来的主键)。
+ - **效果**:
+ - 每次更新页面 JSON,都会为同一个 `webCode` 生成一个新版本的记录,同时逻辑删除旧版本。
+ - 可保留历史记录(在数据库中),但系统只会查询 `is_delete = '0'` 的最新版本。
+
+- **删除**
+ - `deleteHwWebByWebIds` / `deleteHwWebByWebId`:通过 Mapper 将记录逻辑删除。
+
+#### 2.3.3 实现:`HwWebServiceImpl1`(基于 `hw_web1`)
+
+- 与 `HwWebServiceImpl` 基本一致,差异在于**唯一键逻辑**:
+ - 更新时构造 `codeWeb`:
+ - `codeWeb.setWebCode(hwWeb1.getWebCode())`;
+ - `codeWeb.setTypeId(hwWeb1.getTypeId())`;
+ - `codeWeb.setDeviceId(hwWeb1.getDeviceId())`;
+ - 调用 `selectHwWebList(codeWeb)` 按「页面编码 + 设备 + 类型」三元组过滤。
+ - 若存在老记录,同样调用 `deleteHwWebByWebIds` 做逻辑删除,然后插入一条新的记录,并设置 `isDelete = "0"`。
+
+- 这样保证了 (`webCode`, `deviceId`, `typeId`) 组合在「未删除记录」中唯一,并且每次更新产生新版本。
+
+### 2.4 Controller:`HwWebController`
+
+基路径:`/hwWeb`
+
+- **GET `/hwWeb/list`**
+ - 入参:`HwWeb`(作为查询条件)。
+ - 调用:`hwWebService.selectHwWebList(hwWeb)`。
+ - 返回:`TableDataInfo`(分页表格数据)。
+
+- **POST `/hwWeb/export`**
+ - 导出 JSON 配置列表到 Excel,使用 `ExcelUtil`。
+
+- **GET `/hwWeb/{webCode}`**
+ - 通过页面编码 `webCode` 查询单条 JSON 配置。
+
+- **POST `/hwWeb`**
+ - 新增 JSON 配置,直接调用 `insertHwWeb`。
+
+- **PUT `/hwWeb`**
+ - 更新 JSON 配置,调用 `updateHwWeb`:
+ - 实际为「逻辑删除旧版本 + 插入新版本」。
+
+- **DELETE `/hwWeb/{webIds}`**
+ - 批量逻辑删除。
+
+- **GET `/hwWeb/getHwWebList`**
+ - 返回 JSON 配置列表,包一层 `AjaxResult.success(...)`。
+
+---
+
+## 3. 官网菜单:HwWebMenu / HwWebMenu1
+
+### 3.1 实体结构
+
+两者都继承自 `TreeEntity`,支持树形结构字段:`ancestors`、`children` 等。
+
+- **公共字段**(`HwWebMenu`、`HwWebMenu1` 均有):
+ - `webMenuId`:菜单主键 ID
+ - `parent`:父节点 ID(根节点为 `null` 或 `0`)
+ - `status`:状态
+ - `webMenuName`:菜单名称
+ - `tenantId`:租户 ID
+ - `webMenuPic`:图片地址
+ - `webMenuType`:菜单类型
+ - `isDelete`:逻辑删除标志
+ - `webMenuNameEnglish`:英文名称
+
+- **`HwWebMenu1` 特有字段**
+ - `valuel`:对应数据库中的 `value` 字段(字段名有一处 `valuel`,应理解为 value),用于携带菜单值(如路由或业务键)。
+
+**字段类型与数据库列映射:**
+
+- `HwWebMenu`:
+
+| 字段名 | Java 类型 | 数据库列名 | 说明 |
+|--------|-----------|------------|------|
+| `webMenuId` | `Long` | `web_menu_id` | 菜单主键 ID |
+| `parent` | `Long` | `parent` | 父节点 ID(根节点为 0 或 NULL) |
+| `status` | `String` | `status` | 状态 |
+| `webMenuName` | `String` | `web_menu_name` | 菜单名称 |
+| `tenantId` | `Long` | `tenant_id` | 租户 ID |
+| `webMenuPic` | `String` | `web_menu__pic` | 图片地址 |
+| `webMenuType` | `Long` | `web_menu_type` | 菜单类型 |
+| `isDelete` | `String` | `is_delete` | 逻辑删除标志,`0` 未删,`1` 已删 |
+| `webMenuNameEnglish` | `String` | `web_menu_name_english` | 英文菜单名称 |
+
+- `HwWebMenu1`:
+
+| 字段名 | Java 类型 | 数据库列名 | 说明 |
+|--------|-----------|------------|------|
+| `webMenuId` | `Long` | `web_menu_id` | 菜单主键 ID |
+| `parent` | `Long` | `parent` | 父节点 ID(根节点为 0 或 NULL) |
+| `status` | `String` | `status` | 状态 |
+| `webMenuName` | `String` | `web_menu_name` | 菜单名称 |
+| `tenantId` | `Long` | `tenant_id` | 租户 ID |
+| `webMenuPic` | `String` | `web_menu__pic` | 图片地址 |
+| `webMenuType` | `Long` | `web_menu_type` | 菜单类型 |
+| `valuel` | `String` | `value` | 菜单绑定值(如路由、业务编码);实体字段名为 `valuel` |
+| `isDelete` | `String` | `is_delete` | 逻辑删除标志,`0` 未删,`1` 已删 |
+| `webMenuNameEnglish` | `String` | `web_menu_name_english` | 英文菜单名称 |
+
+### 3.2 Mapper & XML 行为
+
+#### 3.2.1 `HwWebMenuMapper` / `HwWebMenuMapper.xml`
+
+- 查询列表 `selectHwWebMenuList(HwWebMenu hwWebMenu)`:
+ - 统一条件:`and is_delete = '0'`。
+ - 支持按 `parent`、`ancestors`、`status`、`webMenuName (like)`、`tenantId`、`webMenuPic`、`webMenuType`、`webMenuNameEnglish (like)` 等过滤。
+
+- 单条查询 `selectHwWebMenuByWebMenuId(Long webMenuId)`:按主键 ID + `is_delete = '0'` 查询。
+
+- 插入 / 更新 / 删除:
+ - 插入时按字段非空写入,并默认 `is_delete = '0'`。
+ - 更新仅更新业务字段,不改变 `is_delete`。
+ - 删除/批量删除采用 `update ... set is_delete = '1'` 实现软删除。
+
+#### 3.2.2 `HwWebMenuMapper1` / `HwWebMenuMapper1.xml`
+
+- 与 `HwWebMenuMapper` 基本一致,差异:
+ - 多了 `value` 字段(对应实体的 `valuel`)。
+ - 查询列表时可按 `value like` 模糊匹配。
+
+### 3.3 Service:菜单树构建算法
+
+`HwWebMenuServiceImpl` 与 `HwWebMenuServiceImpl1` 的结构几乎一致,只是实体类型和 Mapper 不同。核心逻辑:
+
+#### 3.3.1 列表 & 新增/修改/删除
+
+- `selectHwWebMenuList`:直接委托 Mapper,对应接口方法。
+
+- **新增**:`insertHwWebMenu`:
+ - 调用 Mapper 插入,未做额外业务校验。
+
+- **更新**:`updateHwWebMenu`:
+ - 调用 Mapper 更新,仅更新业务字段,不改变 `is_delete`。
+
+- **删除**:`deleteHwWebMenuByWebMenuId` / `deleteHwWebMenuByWebMenuIds`:
+ - 调用 Mapper 删除/批量删除,实现软删除。
+
+#### 3.3.2 构建菜单树:`selectMenuTree` + `buildWebMenuTree`
+
+- **入口**:
+ - `selectMenuTree(HwWebMenu hwWebMenu)` / `selectMenuTree(HwWebMenu1 hwWebMenu1)`:
+ - 先查询平铺列表:`hwWebMenuMapper.selectHwWebMenuList(...)`。
+ - 调用 `buildWebMenuTree(List<...> menus)` 构造树结构。
+
+- **`buildWebMenuTree` 逻辑**(两者完全类似):
+ 1. 收集所有菜单的 `webMenuId` 到列表 `tempList`。
+ 2. 遍历每个菜单 `menu`:
+ - 若满足以下任一条件,则视为根节点:
+ - `menu.getParent() == null`
+ - `menu.getParent() == 0L`
+ - `tempList` 中不包含 `menu.getParent()`(父节点不在当前集合中)
+ - 对于根节点调用 `recursionFn(menus, menu)` 递归设置子节点,并加入返回列表 `returnList`。
+ 3. 若最终 `returnList` 为空(极端情况),则直接返回原始 `menus`。
+
+- **`recursionFn` 递归设置子节点**:
+ - 通过 `getChildList(list, t)` 获取所有 `parent == t.webMenuId` 的子菜单列表。
+ - 调用 `t.setChildren(childList)` 建立树结构。
+ - 对每个子节点 `tChild`,若 `hasChild(list, tChild)` 返回 `true`,则继续递归。
+
+- **`getChildList` / `hasChild`**:
+ - `getChildList` 遍历整张平铺列表,筛选 `parent` 等于当前节点 ID 的记录。
+ - `hasChild` 则判断 `getChildList` 返回列表是否为空。
+ - 使用 `StringUtils.isNotNull(n.getParent())` 判断父节点不为空,提高健壮性。
+
+> 前端只需要调用 `/hwWebMenu/selectMenuTree` 或 `/hwWebMenu1/selectMenuTree`,即可得到带 `children` 字段的完整菜单树结构。
+
+### 3.4 Controller:菜单接口
+
+#### 3.4.1 旧菜单:`HwWebMenuController`(`/hwWebMenu`)
+
+- **GET `/hwWebMenu/list`**:返回菜单列表(平铺)。
+
+- **POST `/hwWebMenu/export`**:导出 Excel。
+
+- **GET `/hwWebMenu/{webMenuId}`**:按 ID 查询明细。
+
+- **POST `/hwWebMenu`**:新增菜单。
+
+- **PUT `/hwWebMenu`**:修改菜单。
+
+- **DELETE `/hwWebMenu/{webMenuIds}`**:批量删除(软删除)。
+
+- **GET `/hwWebMenu/selectMenuTree`**:返回菜单树。
+
+#### 3.4.2 新菜单:`HwWebMenuController1`(`/hwWebMenu1`)
+
+- 与 `HwWebMenuController` 完全对齐,只是实体类型换成 `HwWebMenu1`,以及调用 `IHwWebMenuService1`。
+
+---
+
+## 4. 资料文件与密钥访问:HwWebDocument
+
+### 4.1 实体结构:`HwWebDocument`(表:`hw_web_document`)
+
+- `documentId`:主键(`String` 类型)。
+- `tenantId`:租户 ID。
+- `documentAddress`:真实文件存储地址(URL 或路径)。
+- `webCode`:页面编码,用于和页面/菜单做关联。
+- `secretKey`:访问密钥。
+ - 使用 `@JsonProperty(access = WRITE_ONLY)`,序列化时不会返回给前端。
+- `json`:附加 JSON 信息(例如文件元数据)。
+- `type`:文件类型。(如 pdf、doc 等)
+- `isDelete`:逻辑删除标志。
+- 衍生方法:`getHasSecret()` 返回是否配置了密钥(用于前端判断是否需要弹出输入密钥的对话框)。
+
+**字段类型与数据库列映射:**
+
+| 字段名 | Java 类型 | 数据库列名 | 说明 |
+|--------|-----------|------------|------|
+| `documentId` | `String` | `document_id` | 主键 ID(字符串类型) |
+| `tenantId` | `Long` | `tenant_id` | 租户 ID |
+| `documentAddress` | `String` | `document_address` | 文件存储地址(URL 或路径) |
+| `webCode` | `String` | `web_code` | 页面编码,用于连表或关联页面 |
+| `secretKey` | `String` | `secretKey` | 访问密钥(仅写入,序列化时不返回) |
+| `json` | `String` | `json` | 附加 JSON 信息(如文件元数据) |
+| `type` | `String` | `type` | 文件类型(如 pdf、doc) |
+| `isDelete` | `String` | `is_delete` | 逻辑删除标志,`0` 未删,`1` 已删 |
+
+### 4.2 Mapper & XML 行为
+
+- 列表查询 `selectHwWebDocumentList`:
+ - 强制 `and is_delete = '0'`。
+ - 支持按 `documentId`、`tenantId`、`documentAddress`、`webCode`、`secretKey`、`json`、`type` 等过滤。
+
+- 单条查询 `selectHwWebDocumentByDocumentId`:按主键 + `is_delete = '0'` 查询。
+
+- 插入 `insertHwWebDocument`:
+ - 按字段非空插入,并默认 `is_delete = '0'`。
+
+- 更新 `updateHwWebDocument` 的特殊处理:
+ - 对 `secretKey` 字段使用:
+ ```xml
+
+ secretKey =
+
+ NULL
+ #{secretKey}
+ ,
+
+ ```
+ - 配合 Service 层的「null 转空串」逻辑,实现:
+ - **前端不传或传 null**:认为要清空数据库中的密钥(置为 `NULL`)。
+ - **前端传非空字符串**:更新为新的密钥。
+
+- 删除 / 批量删除:
+ - `deleteHwWebDocumentByDocumentId` / `deleteHwWebDocumentByDocumentIds`:逻辑删除(`is_delete = '1'`)。
+
+### 4.3 Service 实现:`HwWebDocumentServiceImpl`
+
+- **查询/列表/删除**:直接委托 Mapper,对应接口方法。
+
+- **新增**:`insertHwWebDocument`:
+ - 在插入前设置 `createTime = DateUtils.getNowDate()`。
+
+- **更新**:`updateHwWebDocument`:
+ - 如果 `hwWebDocument.getSecretKey() == null`,则强制 `setSecretKey("")`:
+ - 触发 Mapper 中 `` 条件。
+ - 在 XML 中根据空串设置数据库列为 `NULL`,从而**清空密钥**。
+ - 若前端传入非空密钥字符串,则按正常更新覆盖原密钥。
+
+- **密钥验证与安全地址获取**:`verifyAndGetDocumentAddress(String documentId, String providedKey)`
+ 1. 根据 `documentId` 查询文档,若为空:抛出 `ServiceException("文件不存在")`。
+ 2. 取出 `secretKey` 与 `documentAddress`。
+ 3. 若 `secretKey` 为空或空白:
+ - 说明此文件不需要密钥,直接返回 `documentAddress`。
+ 4. 若 `secretKey` 非空:
+ - 将入参 `providedKey` 去空格;若为空:抛出 `ServiceException("密钥不能为空")`。
+ - 若 `secretKey.trim().equals(trimmedProvided)`:返回 `documentAddress`。
+ - 否则抛出 `ServiceException("密钥错误")`。
+
+> 因为这里抛出的都是 `ServiceException`,Controller 层可统一捕获并将 `e.getMessage()` 作为错误提示返回给前端。
+
+### 4.4 Controller:`HwWebDocumentController`(`/hwWebDocument`)
+
+- **GET `/hwWebDocument/list`**
+ - 调用 `startPage()` 分页。
+ - 查询列表后,对每个 `HwWebDocument doc` 做二次处理:
+ - `doc.setSecretKey(null)`:隐藏密钥字段。
+ - 若 `doc.getHasSecret()` 为 `true`:`doc.setDocumentAddress(null)`,隐藏真实文件地址,只暴露「是否加密」。
+ - 通过 `getDataTable(list)` 返回分页数据。
+
+- **POST `/hwWebDocument/export`**
+ - 导出列表到 Excel。
+
+- **GET `/hwWebDocument/{documentId}`**
+ - 查询单条记录,并同样隐藏密钥字段;如果有密钥,也会隐藏 `documentAddress`。
+
+- **POST `/hwWebDocument`**
+ - 新增文档记录。
+
+- **PUT `/hwWebDocument`**
+ - 修改文档记录,调用 `updateHwWebDocument`。通过 Service + XML 的联合逻辑实现「设置/修改/清空密钥」。
+
+- **DELETE `/hwWebDocument/{documentIds}`**
+ - 批量逻辑删除。
+
+- **POST `/hwWebDocument/getSecureDocumentAddress`**
+ - 入参:`SecureDocumentRequest`(包含 `documentId` 和 `providedKey`)。
+ - 调用 `hwWebDocumentService.verifyAndGetDocumentAddress(...)`:
+ - 成功:返回真实 `documentAddress`。
+ - 失败:捕获异常,返回 `error(e.getMessage())`,前端可展示友好提示(文件不存在 / 密钥不能为空 / 密钥错误等)。
+
+---
+
+## 5. 典型业务流程梳理
+
+### 5.1 更新页面 JSON(HwWeb/HwWeb1)
+
+1. 后台管理前端提交新的 JSON 配置到:
+ - `PUT /hwWeb`(基于 `hw_web`),或
+ - `PUT /hwWeb1` 对应的 Controller(若有,对应 `HwWebServiceImpl1`)。
+2. Service 层根据页面编码(以及 `deviceId`、`typeId`)查询已存在记录。
+3. 将旧记录全部逻辑删除(`is_delete = '1'`)。
+4. 插入一条新记录,`isDelete = '0'`。
+5. 前端访问页面时,只会根据编码查询到最新一条记录。
+
+### 5.2 构建官网菜单树(HwWebMenu/HwWebMenu1)
+
+1. 前端调用:
+ - 旧菜单:`GET /hwWebMenu/selectMenuTree`
+ - 新菜单:`GET /hwWebMenu1/selectMenuTree`
+2. Service 调用 Mapper 查询 **平铺列表**(均为 `is_delete = '0'` 的记录)。
+3. 通过 `buildWebMenuTree` 按 `parent` 字段递归构建树形结构:
+ - 识别所有根节点(`parent == null/0` 或父节点不在列表中)。
+ - 递归为每个节点挂接 `children`。
+4. 返回给前端一个包含 `children` 的树形菜单结构,用于渲染导航栏或侧边栏。
+
+### 5.3 受密钥保护的文件访问(HwWebDocument)
+
+1. 后台配置文件记录时可以选择是否设置 `secretKey`:
+ - 不设置:文件公开可访问。
+ - 设置:文件地址将被后端隐藏,需要密钥校验。
+2. 前端先调用 `GET /hwWebDocument/list` 或 `GET /hwWebDocument/{documentId}` 获取文件列表/详情:
+ - 只会看到 `hasSecret`(通过 `getHasSecret()` 间接体现),看不到真实地址。
+3. 用户在前端输入密钥后,前端调用:
+ - `POST /hwWebDocument/getSecureDocumentAddress`,携带 `documentId` 与 `providedKey`。
+4. Service 层校验密钥:
+ - 无密钥:直接返回地址。
+ - 密钥为空:抛错 `"密钥不能为空"`。
+ - 密钥不匹配:抛错 `"密钥错误"`。
+5. 前端拿到真实地址后,可发起真正的文件下载/打开请求。
+
+---
+
+## 6. 核心源码(Controller + Service 实现)
+
+> 以下为与 `HwWeb / HwWeb1 / HwWebDocument / HwWebMenu / HwWebMenu1` 强相关的控制层和服务实现类的完整源码,方便 AI 与人工检索、分析。实体类、接口与 Mapper XML 已在上文详细说明,可在 IDE 中直接定位查看原文件。
+
+### 6.1 Controller 源码
+
+#### 6.1.1 `HwWebController`
+
+```java
+package com.ruoyi.portal.controller;
+
+import java.util.List;
+import java.io.IOException;
+import javax.servlet.http.HttpServletResponse;
+
+import com.ruoyi.portal.domain.HwWeb1;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.log.annotation.Log;
+import com.ruoyi.common.log.enums.BusinessType;
+import com.ruoyi.common.security.annotation.RequiresPermissions;
+import com.ruoyi.portal.domain.HwWeb;
+import com.ruoyi.portal.service.IHwWebService;
+import com.ruoyi.common.core.web.controller.BaseController;
+import com.ruoyi.common.core.web.domain.AjaxResult;
+import com.ruoyi.common.core.utils.poi.ExcelUtil;
+import com.ruoyi.common.core.web.page.TableDataInfo;
+
+/**
+ * haiwei官网jsonController
+ *
+ * @author ruoyi
+ * @date 2025-08-18
+ */
+@RestController
+@RequestMapping("/hwWeb")
+public class HwWebController extends BaseController
+{
+ @Autowired
+ private IHwWebService hwWebService;
+
+ /**
+ * 查询haiwei官网json列表
+ */
+ //@RequiresPermissions("portalhwWeb:list")
+ @GetMapping("/list")
+ public TableDataInfo list(HwWeb hwWeb)
+ {
+// startPage();
+ List list = hwWebService.selectHwWebList(hwWeb);
+ return getDataTable(list);
+ }
+
+ /**
+ * 导出haiwei官网json列表
+ */
+ //@RequiresPermissions("portalhwWeb:export")
+ //@Log(title = "haiwei官网json", businessType = BusinessType.EXPORT)
+ @PostMapping("/export")
+ public void export(HttpServletResponse response, HwWeb hwWeb)
+ {
+ List list = hwWebService.selectHwWebList(hwWeb);
+ ExcelUtil util = new ExcelUtil(HwWeb.class);
+ util.exportExcel(response, list, "haiwei官网json数据");
+ }
+
+ /**
+ * 获取haiwei官网json详细信息
+ */
+ //@RequiresPermissions("portalhwWeb:query")
+ @GetMapping(value = "/{webCode}")
+ public AjaxResult getInfo(@PathVariable("webCode") Long webCode)
+ {
+ return success(hwWebService.selectHwWebByWebcode(webCode));
+ }
+
+ /**
+ * 新增haiwei官网json
+ */
+ //@RequiresPermissions("portalhwWeb:add")
+ //@Log(title = "haiwei官网json", businessType = BusinessType.INSERT)
+ @PostMapping
+ public AjaxResult add(@RequestBody HwWeb hwWeb)
+ {
+ return toAjax(hwWebService.insertHwWeb(hwWeb));
+ }
+
+ /**
+ * 修改haiwei官网json
+ */
+ //@RequiresPermissions("portalhwWeb:edit")
+ //@Log(title = "haiwei官网json", businessType = BusinessType.UPDATE)
+ @PutMapping
+ public AjaxResult edit(@RequestBody HwWeb hwWeb)
+ {
+ int i = hwWebService.updateHwWeb(hwWeb);
+ return toAjax(i);
+ }
+
+ /**
+ * 删除haiwei官网json
+ */
+ //@RequiresPermissions("portalhwWeb:remove")
+ //@Log(title = "haiwei官网json", businessType = BusinessType.DELETE)
+ @DeleteMapping("/{webIds}")
+ public AjaxResult remove(@PathVariable Long[] webIds)
+ {
+ return toAjax(hwWebService.deleteHwWebByWebIds(webIds));
+ }
+
+ @GetMapping("/getHwWebList")
+ public AjaxResult getHwWebList(HwWeb HwWeb)
+ {
+ return success(hwWebService.selectHwWebList(HwWeb)) ;
+ }
+
+}
+```
+
+#### 6.1.2 `HwWebMenuController`
+
+```java
+package com.ruoyi.portal.controller;
+
+import java.util.List;
+import java.io.IOException;
+import javax.servlet.http.HttpServletResponse;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.log.annotation.Log;
+import com.ruoyi.common.log.enums.BusinessType;
+import com.ruoyi.common.security.annotation.RequiresPermissions;
+import com.ruoyi.portal.domain.HwWebMenu;
+import com.ruoyi.portal.service.IHwWebMenuService;
+import com.ruoyi.common.core.web.controller.BaseController;
+import com.ruoyi.common.core.web.domain.AjaxResult;
+import com.ruoyi.common.core.utils.poi.ExcelUtil;
+
+/**
+ * haiwei官网菜单Controller
+ *
+ * @author zch
+ * @date 2025-08-18
+ */
+@RestController
+@RequestMapping("/hwWebMenu")
+public class HwWebMenuController extends BaseController
+{
+ @Autowired
+ private IHwWebMenuService hwWebMenuService;
+
+ /**
+ * 查询haiwei官网菜单列表
+ */
+ //@RequiresPermissions("portalhwWebMenu:list")
+ @GetMapping("/list")
+ public AjaxResult list(HwWebMenu hwWebMenu)
+ {
+ List list = hwWebMenuService.selectHwWebMenuList(hwWebMenu);
+ return success(list);
+ }
+
+ /**
+ * 导出haiwei官网菜单列表
+ */
+ //@RequiresPermissions("portalhwWebMenu:export")
+ //@Log(title = "haiwei官网菜单", businessType = BusinessType.EXPORT)
+ @PostMapping("/export")
+ public void export(HttpServletResponse response, HwWebMenu hwWebMenu)
+ {
+ List list = hwWebMenuService.selectHwWebMenuList(hwWebMenu);
+ ExcelUtil util = new ExcelUtil(HwWebMenu.class);
+ util.exportExcel(response, list, "haiwei官网菜单数据");
+ }
+
+ /**
+ * 获取haiwei官网菜单详细信息
+ */
+ //@RequiresPermissions("portalhwWebMenu:query")
+ @GetMapping(value = "/{webMenuId}")
+ public AjaxResult getInfo(@PathVariable("webMenuId") Long webMenuId)
+ {
+ return success(hwWebMenuService.selectHwWebMenuByWebMenuId(webMenuId));
+ }
+
+ /**
+ * 新增haiwei官网菜单
+ */
+ //@RequiresPermissions("portalhwWebMenu:add")
+ //@Log(title = "haiwei官网菜单", businessType = BusinessType.INSERT)
+ @PostMapping
+ public AjaxResult add(@RequestBody HwWebMenu hwWebMenu)
+ {
+ return toAjax(hwWebMenuService.insertHwWebMenu(hwWebMenu));
+ }
+
+ /**
+ * 修改haiwei官网菜单
+ */
+ //@RequiresPermissions("portalhwWebMenu:edit")
+ //@Log(title = "haiwei官网菜单", businessType = BusinessType.UPDATE)
+ @PutMapping
+ public AjaxResult edit(@RequestBody HwWebMenu hwWebMenu)
+ {
+ return toAjax(hwWebMenuService.updateHwWebMenu(hwWebMenu));
+ }
+
+ /**
+ * 删除haiwei官网菜单
+ */
+ //@RequiresPermissions("portalhwWebMenu:remove")
+ //@Log(title = "haiwei官网菜单", businessType = BusinessType.DELETE)
+ @DeleteMapping("/{webMenuIds}")
+ public AjaxResult remove(@PathVariable Long[] webMenuIds)
+ {
+ return toAjax(hwWebMenuService.deleteHwWebMenuByWebMenuIds(webMenuIds));
+ }
+
+ /**
+ * 获取菜单树列表
+ */
+ @GetMapping("/selectMenuTree")
+ public AjaxResult selectMenuTree(HwWebMenu hwWebMenu){
+ return success(hwWebMenuService.selectMenuTree(hwWebMenu));
+ }
+}
+```
+
+#### 6.1.3 `HwWebMenuController1`
+
+```java
+package com.ruoyi.portal.controller;
+
+import com.ruoyi.common.core.utils.poi.ExcelUtil;
+import com.ruoyi.common.core.web.controller.BaseController;
+import com.ruoyi.common.core.web.domain.AjaxResult;
+import com.ruoyi.common.log.annotation.Log;
+import com.ruoyi.common.log.enums.BusinessType;
+import com.ruoyi.portal.domain.HwWebMenu1;
+import com.ruoyi.portal.service.IHwWebMenuService1;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+
+/**
+ * haiwei官网菜单Controller
+ *
+ * @author zch
+ * @date 2025-08-18
+ */
+@RestController
+@RequestMapping("/hwWebMenu1")
+public class HwWebMenuController1 extends BaseController
+{
+ @Autowired
+ private IHwWebMenuService1 hwWebMenuService1;
+
+ /**
+ * 查询haiwei官网菜单列表
+ */
+ //@RequiresPermissions("portalhwWebMenu:list")
+ @GetMapping("/list")
+ public AjaxResult list(HwWebMenu1 hwWebMenu1)
+ {
+ List list = hwWebMenuService1.selectHwWebMenuList(hwWebMenu1);
+ return success(list);
+ }
+
+ /**
+ * 导出haiwei官网菜单列表
+ */
+ //@RequiresPermissions("portalhwWebMenu:export")
+ //@Log(title = "haiwei官网菜单", businessType = BusinessType.EXPORT)
+ @PostMapping("/export")
+ public void export(HttpServletResponse response, HwWebMenu1 hwWebMenu1)
+ {
+ List list = hwWebMenuService1.selectHwWebMenuList(hwWebMenu1);
+ ExcelUtil util = new ExcelUtil(HwWebMenu1.class);
+ util.exportExcel(response, list, "haiwei官网菜单数据");
+ }
+
+ /**
+ * 获取haiwei官网菜单详细信息
+ */
+ //@RequiresPermissions("portalhwWebMenu:query")
+ @GetMapping(value = "/{webMenuId}")
+ public AjaxResult getInfo(@PathVariable("webMenuId") Long webMenuId)
+ {
+ return success(hwWebMenuService1.selectHwWebMenuByWebMenuId(webMenuId));
+ }
+
+ /**
+ * 新增haiwei官网菜单
+ */
+ //@RequiresPermissions("portalhwWebMenu:add")
+ //@Log(title = "haiwei官网菜单", businessType = BusinessType.INSERT)
+ @PostMapping
+ public AjaxResult add(@RequestBody HwWebMenu1 hwWebMenu1)
+ {
+ return toAjax(hwWebMenuService1.insertHwWebMenu(hwWebMenu1));
+ }
+
+ /**
+ * 修改haiwei官网菜单
+ */
+ //@RequiresPermissions("portalhwWebMenu:edit")
+ //@Log(title = "haiwei官网菜单", businessType = BusinessType.UPDATE)
+ @PutMapping
+ public AjaxResult edit(@RequestBody HwWebMenu1 hwWebMenu1)
+ {
+ return toAjax(hwWebMenuService1.updateHwWebMenu(hwWebMenu1));
+ }
+
+ /**
+ * 删除haiwei官网菜单
+ */
+ //@RequiresPermissions("portalhwWebMenu:remove")
+ //@Log(title = "haiwei官网菜单", businessType = BusinessType.DELETE)
+ @DeleteMapping("/{webMenuIds}")
+ public AjaxResult remove(@PathVariable Long[] webMenuIds)
+ {
+ return toAjax(hwWebMenuService1.deleteHwWebMenuByWebMenuIds(webMenuIds));
+ }
+
+ /**
+ * 获取菜单树列表
+ */
+ @GetMapping("/selectMenuTree")
+ public AjaxResult selectMenuTree(HwWebMenu1 hwWebMenu1){
+ return success(hwWebMenuService1.selectMenuTree(hwWebMenu1));
+ }
+}
+```
+
+#### 6.1.4 `HwWebDocumentController`
+
+```java
+package com.ruoyi.portal.controller;
+
+import java.util.List;
+import java.io.IOException;
+import javax.servlet.http.HttpServletResponse;
+
+import com.ruoyi.portal.domain.SecureDocumentRequest;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.common.log.annotation.Log;
+import com.ruoyi.common.log.enums.BusinessType;
+import com.ruoyi.common.security.annotation.RequiresPermissions;
+import com.ruoyi.portal.domain.HwWebDocument;
+import com.ruoyi.portal.service.IHwWebDocumentService;
+import com.ruoyi.common.core.web.controller.BaseController;
+import com.ruoyi.common.core.web.domain.AjaxResult;
+import com.ruoyi.common.core.utils.poi.ExcelUtil;
+import com.ruoyi.common.core.web.page.TableDataInfo;
+
+/**
+ * Hw资料文件Controller
+ *
+ * @author zch
+ * @date 2025-09-22
+ */
+@RestController
+@RequestMapping("/hwWebDocument")
+public class HwWebDocumentController extends BaseController
+{
+ @Autowired
+ private IHwWebDocumentService hwWebDocumentService;
+
+ /**
+ * 查询Hw资料文件列表
+ */
+// @RequiresPermissions("portal:hwWebDocument:list")
+ @GetMapping("/list")
+ public TableDataInfo list(HwWebDocument hwWebDocument)
+ {
+ startPage();
+ List list = hwWebDocumentService.selectHwWebDocumentList(hwWebDocument);
+ for (HwWebDocument doc : list) {
+ // 隐藏密钥,若设置了密钥则隐藏文件地址
+ doc.setSecretKey(null);
+ if (doc.getHasSecret()) {
+ doc.setDocumentAddress(null);
+ }
+ }
+ return getDataTable(list);
+ }
+
+ /**
+ * 导出Hw资料文件列表
+ */
+// @RequiresPermissions("portal:hwWebDocument:export")
+ //@Log(title = "Hw资料文件", businessType = BusinessType.EXPORT)
+ @PostMapping("/export")
+ public void export(HttpServletResponse response, HwWebDocument hwWebDocument)
+ {
+ List list = hwWebDocumentService.selectHwWebDocumentList(hwWebDocument);
+ ExcelUtil util = new ExcelUtil(HwWebDocument.class);
+ util.exportExcel(response, list, "Hw资料文件数据");
+ }
+
+ /**
+ * 获取Hw资料文件详细信息
+ */
+// @RequiresPermissions("portal:hwWebDocument:query")
+ @GetMapping(value = "/{documentId}")
+ public AjaxResult getInfo(@PathVariable("documentId") String documentId)
+ {
+ HwWebDocument doc = hwWebDocumentService.selectHwWebDocumentByDocumentId(documentId);
+ if (doc != null) {
+ // 隐藏密钥,若设置了密钥则隐藏文件地址
+ doc.setSecretKey(null);
+ if (doc.getHasSecret()) {
+ doc.setDocumentAddress(null);
+ }
+ }
+ return success(doc);
+ }
+
+ /**
+ * 新增Hw资料文件
+ */
+// @RequiresPermissions("portal:hwWebDocument:add")
+ //@Log(title = "Hw资料文件", businessType = BusinessType.INSERT)
+ @PostMapping
+ public AjaxResult add(@RequestBody HwWebDocument hwWebDocument)
+ {
+ return toAjax(hwWebDocumentService.insertHwWebDocument(hwWebDocument));
+ }
+
+ /**
+ * 修改Hw资料文件
+ */
+// @RequiresPermissions("portal:hwWebDocument:edit")
+ //@Log(title = "Hw资料文件", businessType = BusinessType.UPDATE)
+ @PutMapping
+ public AjaxResult edit(@RequestBody HwWebDocument hwWebDocument)
+ {
+ System.out.println(hwWebDocument.getSecretKey());
+ return toAjax(hwWebDocumentService.updateHwWebDocument(hwWebDocument));
+ }
+
+ /**
+ * 删除Hw资料文件
+ */
+// @RequiresPermissions("portal:hwWebDocument:remove")
+ //@Log(title = "Hw资料文件", businessType = BusinessType.DELETE)
+ @DeleteMapping("/{documentIds}")
+ public AjaxResult remove(@PathVariable String[] documentIds)
+ {
+ return toAjax(hwWebDocumentService.deleteHwWebDocumentByDocumentIds(documentIds));
+ }
+
+ /**
+ * 获取安全文件地址
+ */
+// @RequiresPermissions("portal:hwWebDocument:query")
+ //@Log(title = "获取安全文件地址", businessType = BusinessType.OTHER)
+ @PostMapping("/getSecureDocumentAddress")
+ public AjaxResult getSecureDocumentAddress(@RequestBody SecureDocumentRequest request)
+ {
+ try {
+ String address = hwWebDocumentService.verifyAndGetDocumentAddress(request.getDocumentId(), request.getProvidedKey());
+ return success(address);
+ } catch (Exception e) {
+ return error(e.getMessage());
+ }
+ }
+
+}
+```
+
+### 6.2 Service 实现源码
+
+#### 6.2.1 `HwWebServiceImpl`
+
+```java
+package com.ruoyi.portal.service.impl;
+
+import java.util.List;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.ruoyi.portal.mapper.HwWebMapper;
+import com.ruoyi.portal.domain.HwWeb;
+import com.ruoyi.portal.service.IHwWebService;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * haiwei官网jsonService业务层处理
+ *
+ * @author ruoyi
+ * @date 2025-08-18
+ */
+@Service
+public class HwWebServiceImpl implements IHwWebService
+{
+ @Autowired
+ private HwWebMapper hwWebMapper;
+
+ /**
+ * 查询haiwei官网json
+ *
+ * @param webId haiwei官网json主键
+ * @return haiwei官网json
+ */
+ @Override
+ public HwWeb selectHwWebByWebcode(Long webCode)
+ {
+ HwWeb hwWeb = hwWebMapper.selectHwWebByWebcode(webCode);
+ return hwWeb;
+ }
+
+
+ /**
+ * 查询haiwei官网json列表
+ *
+ * @param hwWeb haiwei官网json
+ * @return haiwei官网json
+ */
+ @Override
+ public List selectHwWebList(HwWeb hwWeb)
+ {
+ return hwWebMapper.selectHwWebList(hwWeb);
+ }
+
+ /**
+ * 新增haiwei官网json
+ *
+ * @param hwWeb haiwei官网json
+ * @return 结果
+ */
+ @Override
+ public int insertHwWeb(HwWeb hwWeb)
+ {
+ return hwWebMapper.insertHwWeb(hwWeb);
+ }
+
+ /**
+ * 修改haiwei官网json
+ *
+ * @param hwWeb haiwei官网json
+ * @return 结果
+ */
+ @Override
+ @Transactional( rollbackFor = Exception.class )
+ public int updateHwWeb(HwWeb hwWeb)
+ {
+ HwWeb codeWeb = new HwWeb();
+ //编号唯一
+ codeWeb.setWebCode(hwWeb.getWebCode());
+ List exists = hwWebMapper.selectHwWebList(codeWeb);
+ if (!exists.isEmpty()) {
+ Long[] webIds = exists.stream().map(HwWeb::getWebId).toArray(Long[]::new);
+ //逻辑删除旧纪录
+ hwWebMapper.deleteHwWebByWebIds(webIds);
+ }
+ // 插入新记录,避免复用旧主键
+ // hwWeb.setWebId(null);
+ hwWeb.setIsDelete("0");
+ return hwWebMapper.insertHwWeb(hwWeb);
+ }
+
+ /**
+ * 批量删除haiwei官网json
+ *
+ * @param webIds 需要删除的haiwei官网json主键
+ * @return 结果
+ */
+ @Override
+ public int deleteHwWebByWebIds(Long[] webIds)
+ {
+ return hwWebMapper.deleteHwWebByWebIds(webIds);
+ }
+
+ /**
+ * 删除haiwei官网json信息
+ *
+ * @param webId haiwei官网json主键
+ * @return 结果
+ */
+ @Override
+ public int deleteHwWebByWebId(Long webId)
+ {
+ return hwWebMapper.deleteHwWebByWebId(webId);
+ }
+}
+```
+
+#### 6.2.2 `HwWebServiceImpl1`
+
+```java
+package com.ruoyi.portal.service.impl;
+
+
+import com.ruoyi.portal.mapper.HwWebMapper1;
+import com.ruoyi.portal.service.IHwWebService1;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import java.util.List;
+import com.ruoyi.portal.domain.HwWeb1;
+import com.ruoyi.portal.domain.HwWeb;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * haiwei官网jsonService业务层处理
+ *
+ * @author ruoyi
+ * @date 2025-08-18
+ */
+@Service
+public class HwWebServiceImpl1 implements IHwWebService1
+{
+ @Autowired
+ private HwWebMapper1 hwWebMapper1;
+
+ /**
+ * 查询haiwei官网json
+ *
+ * @param webId haiwei官网json主键
+ * @return haiwei官网json
+ */
+ @Override
+ public HwWeb1 selectHwWebByWebcode(Long webCode)
+ {
+ return hwWebMapper1.selectHwWebByWebcode(webCode);
+ }
+
+ @Override
+ public HwWeb1 selectHwWebOne(HwWeb1 hwWeb1)
+ {
+ return hwWebMapper1.selectHwWebOne(hwWeb1);
+ }
+
+
+ /**
+ * 查询haiwei官网json列表
+ *
+ * @param HwWeb1 haiwei官网json
+ * @return haiwei官网json
+ */
+ @Override
+ public List selectHwWebList(HwWeb1 hwWeb1)
+ {
+ return hwWebMapper1.selectHwWebList(hwWeb1);
+ }
+
+ /**
+ * 新增haiwei官网json
+ *
+ * @param HwWeb1 haiwei官网json
+ * @return 结果
+ */
+ @Override
+ public int insertHwWeb(HwWeb1 hwWeb1)
+ {
+ return hwWebMapper1.insertHwWeb(hwWeb1);
+ }
+
+ /**
+ * 修改haiwei官网json
+ *
+ * @param HwWeb1 haiwei官网json
+ * @return 结果
+ */
+ @Override
+ @Transactional( rollbackFor = Exception.class )
+ public int updateHwWeb(HwWeb1 hwWeb1)
+ {
+ HwWeb1 codeWeb = new HwWeb1();
+ // 编号、typeid、deviceID保证唯一
+ codeWeb.setWebCode(hwWeb1.getWebCode());
+ codeWeb.setTypeId(hwWeb1.getTypeId());
+ codeWeb.setDeviceId(hwWeb1.getDeviceId());
+ List exists = hwWebMapper1.selectHwWebList(codeWeb);
+ if (!exists.isEmpty()) {
+ Long[] webIds = exists.stream().map(HwWeb1::getWebId).toArray(Long[]::new);
+ //逻辑删除旧纪录
+ hwWebMapper1.deleteHwWebByWebIds(webIds);
+ }
+ // 插入新记录,避免复用旧主键
+ // hwWeb1.setWebId(null);
+ hwWeb1.setIsDelete("0");
+ return hwWebMapper1.insertHwWeb(hwWeb1);
+ }
+
+ /**
+ * 批量删除haiwei官网json
+ *
+ * @param webIds 需要删除的haiwei官网json主键
+ * @return 结果
+ */
+ @Override
+ public int deleteHwWebByWebIds(Long[] webIds)
+ {
+ return hwWebMapper1.deleteHwWebByWebIds(webIds);
+ }
+
+ /**
+ * 删除haiwei官网json信息
+ *
+ * @param webId haiwei官网json主键
+ * @return 结果
+ */
+ @Override
+ public int deleteHwWebByWebId(Long webId)
+ {
+ return hwWebMapper1.deleteHwWebByWebId(webId);
+ }
+}
+```
+
+#### 6.2.3 `HwWebMenuServiceImpl`
+
+```java
+package com.ruoyi.portal.service.impl;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import com.ruoyi.common.core.utils.StringUtils;
+import com.ruoyi.portal.domain.HwProductInfoDetail;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.ruoyi.portal.mapper.HwWebMenuMapper;
+import com.ruoyi.portal.domain.HwWebMenu;
+import com.ruoyi.portal.service.IHwWebMenuService;
+
+/**
+ * haiwei官网菜单Service业务层处理
+ *
+ * @author zch
+ * @date 2025-08-18
+ */
+@Service
+public class HwWebMenuServiceImpl implements IHwWebMenuService
+{
+ @Autowired
+ private HwWebMenuMapper hwWebMenuMapper;
+
+ /**
+ * 查询haiwei官网菜单
+ *
+ * @param webMenuId haiwei官网菜单主键
+ * @return haiwei官网菜单
+ */
+ @Override
+ public HwWebMenu selectHwWebMenuByWebMenuId(Long webMenuId)
+ {
+ return hwWebMenuMapper.selectHwWebMenuByWebMenuId(webMenuId);
+ }
+
+ /**
+ * 查询haiwei官网菜单列表
+ *
+ * @param hwWebMenu haiwei官网菜单
+ * @return haiwei官网菜单
+ */
+ @Override
+ public List