first commit

This commit is contained in:
lcj
2025-07-23 16:38:10 +08:00
commit 33423fc4d2
1640 changed files with 153857 additions and 0 deletions

27
ruoyi-modules/pom.xml Normal file
View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>xinnengyuan</artifactId>
<groupId>org.dromara</groupId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<modules>
<module>ruoyi-generator</module>
<module>ruoyi-job</module>
<module>ruoyi-system</module>
<module>ruoyi-workflow</module>
<module>ruoyi-app</module>
</modules>
<artifactId>ruoyi-modules</artifactId>
<packaging>pom</packaging>
<description>
ruoyi-modules 业务模块
</description>
</project>

View File

@ -0,0 +1,112 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-modules</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>
<artifactId>ruoyi-app</artifactId>
<dependencies>
<!-- 通用工具-->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-core</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-doc</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-mybatis</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-translation</artifactId>
</dependency>
<!-- OSS功能模块 -->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-oss</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-log</artifactId>
</dependency>
<!-- excel-->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-excel</artifactId>
</dependency>
<!-- SMS功能模块 -->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-sms</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-tenant</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-security</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-web</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-idempotent</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-sensitive</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-encrypt</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-sse</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-jts</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-system</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,52 @@
package org.dromara.app.system.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import jakarta.annotation.Resource;
import org.dromara.app.system.domain.dto.menu.AppMenuCreateReq;
import org.dromara.app.system.domain.dto.menu.AppMenuUpdateReq;
import org.dromara.app.system.service.AppMenuService;
import org.dromara.common.core.domain.R;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.web.core.BaseController;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
/**
* app菜单信息
*
* @author lilemy
* @date 2025-07-23 15:13
*/
@Validated
@RestController
@RequestMapping("/app/system/menu")
public class AppMenuController extends BaseController {
@Resource
private AppMenuService appMenuService;
/**
* 新增app菜单信息
*/
@SaCheckPermission("contractor:contractorToolRecord:add")
@Log(title = "app菜单信息", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated @RequestBody AppMenuCreateReq req) {
return toAjax(appMenuService.insertByReq(req));
}
/**
* 修改app菜单信息
*/
@SaCheckPermission("contractor:contractorToolRecord:edit")
@Log(title = "app菜单信息", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated @RequestBody AppMenuUpdateReq req) {
return toAjax(appMenuService.updateByReq(req));
}
}

View File

@ -0,0 +1,91 @@
package org.dromara.app.system.domain;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import java.io.Serial;
/**
* app菜单
*
* @TableName app_menu
*/
@Data
@TableName(value = "app_menu")
@EqualsAndHashCode(callSuper = true)
public class AppMenu extends BaseEntity {
/**
* 主键ID
*/
@TableId(value = "id")
private Long id;
/**
* 菜单名称
*/
private String menuName;
/**
* 父菜单ID
*/
private Long parentId;
/**
* 显示顺序
*/
private Integer orderNum;
/**
* 路由地址
*/
private String path;
/**
* 组件路径
*/
private String component;
/**
* 路由参数
*/
private String queryParam;
/**
* 菜单类型M目录 C菜单 F按钮
*/
private String menuType;
/**
* 显示状态0显示 1隐藏
*/
private String visible;
/**
* 菜单状态0正常 1停用
*/
private String status;
/**
* 权限标识
*/
private String perms;
/**
* 菜单图标
*/
private String icon;
/**
* 备注
*/
private String remark;
@Serial
@TableField(exist = false)
private static final long serialVersionUID = 1L;
}

View File

@ -0,0 +1,61 @@
package org.dromara.app.system.domain;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import java.io.Serial;
/**
* app角色
*
* @TableName app_role
*/
@EqualsAndHashCode(callSuper = true)
@TableName(value = "app_role")
@Data
public class AppRole extends BaseEntity {
/**
* 主键ID
*/
@TableId(value = "id")
private Long id;
/**
* 角色名称
*/
private String roleName;
/**
* 角色权限字符串
*/
private String roleKey;
/**
* 显示顺序
*/
private Integer orderNum;
/**
* 数据范围1全部数据权限 2自定数据权限
*/
private String dataScope;
/**
* 角色状态0正常 1停用
*/
private String status;
/**
* 备注
*/
private String remark;
@Serial
@TableField(exist = false)
private static final long serialVersionUID = 1L;
}

View File

@ -0,0 +1,36 @@
package org.dromara.app.system.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* app角色和菜单关联
*
* @TableName app_role_menu
*/
@TableName(value = "app_role_menu")
@Data
public class AppRoleMenu implements Serializable {
/**
* 角色ID
*/
@TableId(type = IdType.INPUT)
private Long roleId;
/**
* 菜单ID
*/
private Long menuId;
@Serial
@TableField(exist = false)
private static final long serialVersionUID = 1L;
}

View File

@ -0,0 +1,36 @@
package org.dromara.app.system.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* app用户和角色关联
*
* @TableName app_user_role
*/
@TableName(value = "app_user_role")
@Data
public class AppUserRole implements Serializable {
/**
* 用户ID
*/
@TableId(type = IdType.INPUT)
private Long userId;
/**
* 角色ID
*/
private Long roleId;
@Serial
@TableField(exist = false)
private static final long serialVersionUID = 1L;
}

View File

@ -0,0 +1,84 @@
package org.dromara.app.system.domain.dto.menu;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* @author lilemy
* @date 2025-07-23 14:53
*/
@Data
public class AppMenuCreateReq implements Serializable {
@Serial
private static final long serialVersionUID = 5959650762623651138L;
/**
* 菜单名称
*/
@NotBlank(message = "菜单名称不能为空")
private String menuName;
/**
* 父菜单ID
*/
@NotNull(message = "父菜单不能为空")
private Long parentId;
/**
* 显示顺序
*/
private Integer orderNum;
/**
* 路由地址
*/
@NotBlank(message = "路由地址不能为空")
private String path;
/**
* 组件路径
*/
private String component;
/**
* 路由参数
*/
private String queryParam;
/**
* 菜单类型M目录 C菜单 F按钮
*/
@NotBlank(message = "菜单类型不能为空")
private String menuType;
/**
* 显示状态0显示 1隐藏
*/
private String visible;
/**
* 菜单状态0正常 1停用
*/
private String status;
/**
* 权限标识
*/
private String perms;
/**
* 菜单图标
*/
private String icon;
/**
* 备注
*/
private String remark;
}

View File

@ -0,0 +1,38 @@
package org.dromara.app.system.domain.dto.menu;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* @author lilemy
* @date 2025-07-23 14:55
*/
@Data
public class AppMenuQueryReq implements Serializable {
@Serial
private static final long serialVersionUID = 430846650497878659L;
/**
* 菜单名称
*/
private String menuName;
/**
* 菜单类型M目录 C菜单 F按钮
*/
private String menuType;
/**
* 显示状态0显示 1隐藏
*/
private String visible;
/**
* 菜单状态0正常 1停用
*/
private String status;
}

View File

@ -0,0 +1,90 @@
package org.dromara.app.system.domain.dto.menu;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* @author lilemy
* @date 2025-07-23 14:54
*/
@Data
public class AppMenuUpdateReq implements Serializable {
@Serial
private static final long serialVersionUID = -2001348573190432371L;
/**
* 主键ID
*/
@NotNull(message = "菜单ID不能为空")
private Long id;
/**
* 菜单名称
*/
@NotBlank(message = "菜单名称不能为空")
private String menuName;
/**
* 父菜单ID
*/
@NotNull(message = "父菜单不能为空")
private Long parentId;
/**
* 显示顺序
*/
@NotNull(message = "显示顺序不能为空")
private Integer orderNum;
/**
* 路由地址
*/
@NotBlank(message = "路由地址不能为空")
private String path;
/**
* 组件路径
*/
private String component;
/**
* 路由参数
*/
private String queryParam;
/**
* 菜单类型M目录 C菜单 F按钮
*/
@NotBlank(message = "菜单类型不能为空")
private String menuType;
/**
* 显示状态0显示 1隐藏
*/
private String visible;
/**
* 菜单状态0正常 1停用
*/
private String status;
/**
* 权限标识
*/
private String perms;
/**
* 菜单图标
*/
private String icon;
/**
* 备注
*/
private String remark;
}

View File

@ -0,0 +1,18 @@
package org.dromara.app.system.domain.dto.role;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* @author lilemy
* @date 2025-07-23 14:55
*/
@Data
public class AppRoleCreateReq implements Serializable {
@Serial
private static final long serialVersionUID = -282693316523798371L;
}

View File

@ -0,0 +1,18 @@
package org.dromara.app.system.domain.dto.role;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* @author lilemy
* @date 2025-07-23 14:55
*/
@Data
public class AppRoleQueryReq implements Serializable {
@Serial
private static final long serialVersionUID = -7355276515541220943L;
}

View File

@ -0,0 +1,18 @@
package org.dromara.app.system.domain.dto.role;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* @author lilemy
* @date 2025-07-23 14:55
*/
@Data
public class AppRoleUpdateReq implements Serializable {
@Serial
private static final long serialVersionUID = 6996190784775829966L;
}

View File

@ -0,0 +1,83 @@
package org.dromara.app.system.domain.vo.menu;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* @author lilemy
* @date 2025-07-23 14:50
*/
@Data
public class AppMenuVo implements Serializable {
@Serial
private static final long serialVersionUID = 466794582748236105L;
/**
* 主键ID
*/
private Long id;
/**
* 菜单名称
*/
private String menuName;
/**
* 父菜单ID
*/
private Long parentId;
/**
* 显示顺序
*/
private Integer orderNum;
/**
* 路由地址
*/
private String path;
/**
* 组件路径
*/
private String component;
/**
* 路由参数
*/
private String queryParam;
/**
* 菜单类型M目录 C菜单 F按钮
*/
private String menuType;
/**
* 显示状态0显示 1隐藏
*/
private String visible;
/**
* 菜单状态0正常 1停用
*/
private String status;
/**
* 权限标识
*/
private String perms;
/**
* 菜单图标
*/
private String icon;
/**
* 备注
*/
private String remark;
}

View File

@ -0,0 +1,53 @@
package org.dromara.app.system.domain.vo.role;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* @author lilemy
* @date 2025-07-23 14:50
*/
@Data
public class AppRoleVo implements Serializable {
@Serial
private static final long serialVersionUID = 222190798885808758L;
/**
* 主键ID
*/
private Long id;
/**
* 角色名称
*/
private String roleName;
/**
* 角色权限字符串
*/
private String roleKey;
/**
* 显示顺序
*/
private Integer orderNum;
/**
* 数据范围1全部数据权限 2自定数据权限
*/
private String dataScope;
/**
* 角色状态0正常 1停用
*/
private String status;
/**
* 备注
*/
private String remark;
}

View File

@ -0,0 +1,17 @@
package org.dromara.app.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.dromara.app.system.domain.AppMenu;
/**
* @author lilemy
* @description 针对表【app_menu(app菜单)】的数据库操作Mapper
* @createDate 2025-07-23 14:39:56
*/
public interface AppMenuMapper extends BaseMapper<AppMenu> {
}

View File

@ -0,0 +1,17 @@
package org.dromara.app.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.dromara.app.system.domain.AppRole;
/**
* @author lilemy
* @description 针对表【app_role(app角色)】的数据库操作Mapper
* @createDate 2025-07-23 14:39:56
*/
public interface AppRoleMapper extends BaseMapper<AppRole> {
}

View File

@ -0,0 +1,17 @@
package org.dromara.app.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.dromara.app.system.domain.AppRoleMenu;
/**
* @author lilemy
* @description 针对表【app_role_menu(app角色和菜单关联)】的数据库操作Mapper
* @createDate 2025-07-23 14:39:56
*/
public interface AppRoleMenuMapper extends BaseMapper<AppRoleMenu> {
}

View File

@ -0,0 +1,17 @@
package org.dromara.app.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.dromara.app.system.domain.AppUserRole;
/**
* @author lilemy
* @description 针对表【app_user_role(app用户和角色关联)】的数据库操作Mapper
* @createDate 2025-07-23 14:39:56
*/
public interface AppUserRoleMapper extends BaseMapper<AppUserRole> {
}

View File

@ -0,0 +1,59 @@
package org.dromara.app.system.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import org.dromara.app.system.domain.AppMenu;
import org.dromara.app.system.domain.dto.menu.AppMenuCreateReq;
import org.dromara.app.system.domain.dto.menu.AppMenuQueryReq;
import org.dromara.app.system.domain.dto.menu.AppMenuUpdateReq;
import org.dromara.app.system.domain.vo.menu.AppMenuVo;
/**
* @author lilemy
* @description 针对表【app_menu(app菜单)】的数据库操作Service
* @createDate 2025-07-23 14:39:56
*/
public interface AppMenuService extends IService<AppMenu> {
/**
* 新增app菜单
*
* @param req app菜单创建请求
* @return 是否成功
*/
Boolean insertByReq(AppMenuCreateReq req);
/**
* 修改app菜单
*
* @param req app菜单更新请求
* @return 是否成功
*/
Boolean updateByReq(AppMenuUpdateReq req);
/**
* 获取app菜单视图
*
* @param menu app菜单
* @return app菜单视图
*/
AppMenuVo getVo(AppMenu menu);
/**
* 构建查询条件封装
*
* @param req 查询条件
* @return 查询条件封装
*/
LambdaQueryWrapper<AppMenu> buildQueryWrapper(AppMenuQueryReq req);
/**
* 获取app菜单分页对象视图
*
* @param menuPage app菜单分页对象
* @return app菜单分页对象视图
*/
Page<AppMenuVo> getVoPage(Page<AppMenu> menuPage);
}

View File

@ -0,0 +1,13 @@
package org.dromara.app.system.service;
import com.baomidou.mybatisplus.extension.service.IService;
import org.dromara.app.system.domain.AppRoleMenu;
/**
* @author lilemy
* @description 针对表【app_role_menu(app角色和菜单关联)】的数据库操作Service
* @createDate 2025-07-23 14:39:56
*/
public interface AppRoleMenuService extends IService<AppRoleMenu> {
}

View File

@ -0,0 +1,13 @@
package org.dromara.app.system.service;
import com.baomidou.mybatisplus.extension.service.IService;
import org.dromara.app.system.domain.AppRole;
/**
* @author lilemy
* @description 针对表【app_role(app角色)】的数据库操作Service
* @createDate 2025-07-23 14:39:56
*/
public interface AppRoleService extends IService<AppRole> {
}

View File

@ -0,0 +1,13 @@
package org.dromara.app.system.service;
import com.baomidou.mybatisplus.extension.service.IService;
import org.dromara.app.system.domain.AppUserRole;
/**
* @author lilemy
* @description 针对表【app_user_role(app用户和角色关联)】的数据库操作Service
* @createDate 2025-07-23 14:39:56
*/
public interface AppUserRoleService extends IService<AppUserRole> {
}

View File

@ -0,0 +1,118 @@
package org.dromara.app.system.service.impl;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.dromara.app.system.domain.AppMenu;
import org.dromara.app.system.domain.dto.menu.AppMenuCreateReq;
import org.dromara.app.system.domain.dto.menu.AppMenuQueryReq;
import org.dromara.app.system.domain.dto.menu.AppMenuUpdateReq;
import org.dromara.app.system.domain.vo.menu.AppMenuVo;
import org.dromara.app.system.mapper.AppMenuMapper;
import org.dromara.app.system.service.AppMenuService;
import org.dromara.common.core.utils.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @author lilemy
* @description 针对表【app_menu(app菜单)】的数据库操作Service实现
* @createDate 2025-07-23 14:39:56
*/
@Service
public class AppMenuServiceImpl extends ServiceImpl<AppMenuMapper, AppMenu>
implements AppMenuService {
/**
* 新增app菜单
*
* @param req app菜单创建请求
* @return 是否成功
*/
@Override
public Boolean insertByReq(AppMenuCreateReq req) {
return null;
}
/**
* 修改app菜单
*
* @param req app菜单更新请求
* @return 是否成功
*/
@Override
public Boolean updateByReq(AppMenuUpdateReq req) {
return null;
}
/**
* 获取app菜单视图
*
* @param menu app菜单
* @return app菜单视图
*/
@Override
public AppMenuVo getVo(AppMenu menu) {
AppMenuVo menuVo = new AppMenuVo();
if (menu == null) {
return menuVo;
}
BeanUtils.copyProperties(menu, menuVo);
return menuVo;
}
/**
* 构建查询条件封装
*
* @param req 查询条件
* @return 查询条件封装
*/
@Override
public LambdaQueryWrapper<AppMenu> buildQueryWrapper(AppMenuQueryReq req) {
LambdaQueryWrapper<AppMenu> lqw = new LambdaQueryWrapper<>();
if (req == null) {
return lqw;
}
String menuName = req.getMenuName();
String menuType = req.getMenuType();
String visible = req.getVisible();
String status = req.getStatus();
lqw.like(StringUtils.isNotBlank(menuName), AppMenu::getMenuName, menuName);
lqw.eq(StringUtils.isNotBlank(menuType), AppMenu::getMenuType, menuType);
lqw.eq(StringUtils.isNotBlank(visible), AppMenu::getVisible, visible);
lqw.eq(StringUtils.isNotBlank(status), AppMenu::getStatus, status);
return lqw;
}
/**
* 获取app菜单分页对象视图
*
* @param menuPage app菜单分页对象
* @return app菜单分页对象视图
*/
@Override
public Page<AppMenuVo> getVoPage(Page<AppMenu> menuPage) {
List<AppMenu> menuList = menuPage.getRecords();
Page<AppMenuVo> menuVoPage = new Page<>(
menuPage.getCurrent(),
menuPage.getSize(),
menuPage.getTotal());
if (CollUtil.isEmpty(menuList)) {
return menuVoPage;
}
List<AppMenuVo> menuVoList = menuList.stream().map(entity -> {
AppMenuVo menuVo = new AppMenuVo();
BeanUtils.copyProperties(entity, menuVo);
return menuVo;
}).toList();
menuVoPage.setRecords(menuVoList);
return menuVoPage;
}
}

View File

@ -0,0 +1,22 @@
package org.dromara.app.system.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.dromara.app.system.domain.AppRoleMenu;
import org.dromara.app.system.mapper.AppRoleMenuMapper;
import org.dromara.app.system.service.AppRoleMenuService;
import org.springframework.stereotype.Service;
/**
* @author lilemy
* @description 针对表【app_role_menu(app角色和菜单关联)】的数据库操作Service实现
* @createDate 2025-07-23 14:39:56
*/
@Service
public class AppRoleMenuServiceImpl extends ServiceImpl<AppRoleMenuMapper, AppRoleMenu>
implements AppRoleMenuService {
}

View File

@ -0,0 +1,22 @@
package org.dromara.app.system.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.dromara.app.system.domain.AppRole;
import org.dromara.app.system.mapper.AppRoleMapper;
import org.dromara.app.system.service.AppRoleService;
import org.springframework.stereotype.Service;
/**
* @author lilemy
* @description 针对表【app_role(app角色)】的数据库操作Service实现
* @createDate 2025-07-23 14:39:56
*/
@Service
public class AppRoleServiceImpl extends ServiceImpl<AppRoleMapper, AppRole>
implements AppRoleService {
}

View File

@ -0,0 +1,22 @@
package org.dromara.app.system.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.dromara.app.system.domain.AppUserRole;
import org.dromara.app.system.mapper.AppUserRoleMapper;
import org.dromara.app.system.service.AppUserRoleService;
import org.springframework.stereotype.Service;
/**
* @author lilemy
* @description 针对表【app_user_role(app用户和角色关联)】的数据库操作Service实现
* @createDate 2025-07-23 14:39:56
*/
@Service
public class AppUserRoleServiceImpl extends ServiceImpl<AppUserRoleMapper, AppUserRole>
implements AppUserRoleService {
}

View File

@ -0,0 +1,34 @@
<?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="org.dromara.app.system.mapper.AppMenuMapper">
<resultMap id="BaseResultMap" type="org.dromara.app.system.domain.AppMenu">
<id property="id" column="id"/>
<result property="menuName" column="menu_name"/>
<result property="parentId" column="parent_id"/>
<result property="orderNum" column="order_num"/>
<result property="path" column="path"/>
<result property="component" column="component"/>
<result property="queryParam" column="query_param"/>
<result property="menuType" column="menu_type"/>
<result property="visible" column="visible"/>
<result property="status" column="status"/>
<result property="perms" column="perms"/>
<result property="icon" column="icon"/>
<result property="createDept" column="create_dept"/>
<result property="remark" column="remark"/>
<result property="createBy" column="create_by"/>
<result property="updateBy" column="update_by"/>
<result property="createTime" column="create_time"/>
<result property="updateTime" column="update_time"/>
</resultMap>
<sql id="Base_Column_List">
id,menu_name,parent_id,order_num,path,component,
query_param,menu_type,visible,status,perms,
icon,create_dept,remark,create_by,update_by,
create_time,update_time
</sql>
</mapper>

View File

@ -0,0 +1,27 @@
<?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="org.dromara.app.system.mapper.AppRoleMapper">
<resultMap id="BaseResultMap" type="org.dromara.app.system.domain.AppRole">
<id property="id" column="id"/>
<result property="roleName" column="role_name"/>
<result property="roleKey" column="role_key"/>
<result property="orderNum" column="order_num"/>
<result property="dataScope" column="data_scope"/>
<result property="status" column="status"/>
<result property="createDept" column="create_dept"/>
<result property="remark" column="remark"/>
<result property="createBy" column="create_by"/>
<result property="updateBy" column="update_by"/>
<result property="createTime" column="create_time"/>
<result property="updateTime" column="update_time"/>
</resultMap>
<sql id="Base_Column_List">
id,role_name,role_key,order_num,data_scope,status,
create_dept,remark,create_by,update_by,create_time,
update_time
</sql>
</mapper>

View File

@ -0,0 +1,15 @@
<?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="org.dromara.app.system.mapper.AppRoleMenuMapper">
<resultMap id="BaseResultMap" type="org.dromara.app.system.domain.AppRoleMenu">
<id property="roleId" column="role_id"/>
<id property="menuId" column="menu_id"/>
</resultMap>
<sql id="Base_Column_List">
role_id,menu_id
</sql>
</mapper>

View File

@ -0,0 +1,15 @@
<?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="org.dromara.app.system.mapper.AppUserRoleMapper">
<resultMap id="BaseResultMap" type="org.dromara.app.system.domain.AppUserRole">
<id property="userId" column="user_id"/>
<id property="roleId" column="role_id"/>
</resultMap>
<sql id="Base_Column_List">
user_id,role_id
</sql>
</mapper>

View File

@ -0,0 +1,84 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-modules</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ruoyi-generator</artifactId>
<description>
generator 代码生成
</description>
<dependencies>
<!-- 通用工具-->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-core</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-doc</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-mybatis</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-web</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-log</artifactId>
</dependency>
<!--velocity代码生成使用模板 -->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
</dependency>
<dependency>
<groupId>org.anyline</groupId>
<artifactId>anyline-environment-spring-data-jdbc</artifactId>
<version>${anyline.version}</version>
</dependency>
<dependency>
<groupId>org.anyline</groupId>
<artifactId>anyline-data-jdbc-mysql</artifactId>
<version>${anyline.version}</version>
</dependency>
<!-- anyline支持100+种类型数据库 添加对应的jdbc依赖与anyline对应数据库依赖包即可 -->
<!-- <dependency>-->
<!-- <groupId>org.anyline</groupId>-->
<!-- <artifactId>anyline-data-jdbc-oracle</artifactId>-->
<!-- <version>${anyline.version}</version>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>org.anyline</groupId>-->
<!-- <artifactId>anyline-data-jdbc-postgresql</artifactId>-->
<!-- <version>${anyline.version}</version>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>org.anyline</groupId>-->
<!-- <artifactId>anyline-data-jdbc-mssql</artifactId>-->
<!-- <version>${anyline.version}</version>-->
<!-- </dependency>-->
</dependencies>
</project>

View File

@ -0,0 +1,73 @@
package org.dromara.generator.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
/**
* 读取代码生成相关配置
*
* @author ruoyi
*/
@Component
@ConfigurationProperties(prefix = "gen")
@PropertySource(value = {"classpath:generator.yml"}, encoding = "UTF-8")
public class GenConfig {
/**
* 作者
*/
public static String author;
/**
* 生成包路径
*/
public static String packageName;
/**
* 自动去除表前缀默认是false
*/
public static boolean autoRemovePre;
/**
* 表前缀(类名不会包含表前缀)
*/
public static String tablePrefix;
public static String getAuthor() {
return author;
}
@Value("${author}")
public void setAuthor(String author) {
GenConfig.author = author;
}
public static String getPackageName() {
return packageName;
}
@Value("${packageName}")
public void setPackageName(String packageName) {
GenConfig.packageName = packageName;
}
public static boolean getAutoRemovePre() {
return autoRemovePre;
}
@Value("${autoRemovePre}")
public void setAutoRemovePre(boolean autoRemovePre) {
GenConfig.autoRemovePre = autoRemovePre;
}
public static String getTablePrefix() {
return tablePrefix;
}
@Value("${tablePrefix}")
public void setTablePrefix(String tablePrefix) {
GenConfig.tablePrefix = tablePrefix;
}
}

View File

@ -0,0 +1,105 @@
package org.dromara.generator.config;
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.anyline.data.datasource.DataSourceMonitor;
import org.anyline.data.runtime.DataRuntime;
import org.anyline.util.ConfigTable;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.util.HashMap;
import java.util.Map;
/**
* anyline 适配 动态数据源改造
*
* @author Lion Li
*/
@Slf4j
@Component
public class MyBatisDataSourceMonitor implements DataSourceMonitor {
public MyBatisDataSourceMonitor() {
// 调整执行模式为自定义
ConfigTable.KEEP_ADAPTER = 2;
// 禁用缓存
ConfigTable.METADATA_CACHE_SCOPE = 0;
}
private final Map<String, String> features = new HashMap<>();
/**
* 数据源特征 用来定准 adapter 包含数据库或JDBC协议关键字<br/>
* 一般会通过 产品名_url 合成 如果返回null 上层方法会通过driver_产品名_url合成
*
* @param datasource 数据源
* @return String 返回null由上层自动提取
*/
@Override
public String feature(DataRuntime runtime, Object datasource) {
String feature = null;
if (datasource instanceof JdbcTemplate jdbc) {
DataSource ds = jdbc.getDataSource();
if (ds instanceof DynamicRoutingDataSource) {
String key = DynamicDataSourceContextHolder.peek();
feature = features.get(key);
if (null == feature) {
Connection con = null;
try {
con = DataSourceUtils.getConnection(ds);
DatabaseMetaData meta = con.getMetaData();
String url = meta.getURL();
feature = meta.getDatabaseProductName().toLowerCase().replace(" ", "") + "_" + url;
features.put(key, feature);
} catch (Exception e) {
log.error(e.getMessage(), e);
} finally {
if (null != con && !DataSourceUtils.isConnectionTransactional(con, ds)) {
DataSourceUtils.releaseConnection(con, ds);
}
}
}
}
}
return feature;
}
/**
* 数据源唯一标识 如果不实现则默认feature
* @param datasource 数据源
* @return String 返回null由上层自动提取
*/
@Override
public String key(DataRuntime runtime, Object datasource) {
if(datasource instanceof JdbcTemplate jdbc){
DataSource ds = jdbc.getDataSource();
if(ds instanceof DynamicRoutingDataSource){
return DynamicDataSourceContextHolder.peek();
}
}
return runtime.getKey();
}
/**
* ConfigTable.KEEP_ADAPTER=2 : 根据当前接口判断是否保持同一个数据源绑定同一个adapter<br/>
* DynamicRoutingDataSource类型的返回false,因为同一个DynamicRoutingDataSource可能对应多类数据库, 如果项目中只有一种数据库 应该直接返回true
*
* @param datasource 数据源
* @return boolean
*/
@Override
public boolean keepAdapter(DataRuntime runtime, Object datasource) {
if (datasource instanceof JdbcTemplate jdbc) {
DataSource ds = jdbc.getDataSource();
return !(ds instanceof DynamicRoutingDataSource);
}
return true;
}
}

View File

@ -0,0 +1,186 @@
package org.dromara.generator.constant;
/**
* 代码生成通用常量
*
* @author ruoyi
*/
public interface GenConstants {
/**
* 单表(增删改查)
*/
String TPL_CRUD = "crud";
/**
* 树表(增删改查)
*/
String TPL_TREE = "tree";
/**
* 树编码字段
*/
String TREE_CODE = "treeCode";
/**
* 树父编码字段
*/
String TREE_PARENT_CODE = "treeParentCode";
/**
* 树名称字段
*/
String TREE_NAME = "treeName";
/**
* 上级菜单ID字段
*/
String PARENT_MENU_ID = "parentMenuId";
/**
* 上级菜单名称字段
*/
String PARENT_MENU_NAME = "parentMenuName";
/**
* 数据库字符串类型
*/
String[] COLUMNTYPE_STR = {"char", "varchar", "enum", "set", "nchar", "nvarchar", "varchar2", "nvarchar2"};
/**
* 数据库文本类型
*/
String[] COLUMNTYPE_TEXT = {"tinytext", "text", "mediumtext", "longtext", "binary", "varbinary", "blob",
"ntext", "image", "bytea"};
/**
* 数据库时间类型
*/
String[] COLUMNTYPE_TIME = {"datetime", "time", "date", "timestamp", "year", "interval",
"smalldatetime", "datetime2", "datetimeoffset", "timestamptz"};
/**
* 数据库数字类型
*/
String[] COLUMNTYPE_NUMBER = {"tinyint", "smallint", "mediumint", "int", "int2", "int4", "int8", "number", "integer",
"bit", "bigint", "float", "float4", "float8", "double", "decimal", "numeric", "real", "double precision",
"smallserial", "serial", "bigserial", "money", "smallmoney"};
/**
* BO对象 不需要添加字段
*/
String[] COLUMNNAME_NOT_ADD = {"create_dept", "create_by", "create_time", "del_flag", "update_by",
"update_time", "version", "tenant_id"};
/**
* BO对象 不需要编辑字段
*/
String[] COLUMNNAME_NOT_EDIT = {"create_dept", "create_by", "create_time", "del_flag", "update_by",
"update_time", "version", "tenant_id"};
/**
* VO对象 不需要返回字段
*/
String[] COLUMNNAME_NOT_LIST = {"create_dept", "create_by", "create_time", "del_flag", "update_by",
"update_time", "version", "tenant_id"};
/**
* BO对象 不需要查询字段
*/
String[] COLUMNNAME_NOT_QUERY = {"id", "create_dept", "create_by", "create_time", "del_flag", "update_by",
"update_time", "remark", "version", "tenant_id"};
/**
* Entity基类字段
*/
String[] BASE_ENTITY = {"createDept", "createBy", "createTime", "updateBy", "updateTime", "tenantId"};
/**
* 文本框
*/
String HTML_INPUT = "input";
/**
* 文本域
*/
String HTML_TEXTAREA = "textarea";
/**
* 下拉框
*/
String HTML_SELECT = "select";
/**
* 单选框
*/
String HTML_RADIO = "radio";
/**
* 复选框
*/
String HTML_CHECKBOX = "checkbox";
/**
* 日期控件
*/
String HTML_DATETIME = "datetime";
/**
* 图片上传控件
*/
String HTML_IMAGE_UPLOAD = "imageUpload";
/**
* 文件上传控件
*/
String HTML_FILE_UPLOAD = "fileUpload";
/**
* 富文本控件
*/
String HTML_EDITOR = "editor";
/**
* 字符串类型
*/
String TYPE_STRING = "String";
/**
* 整型
*/
String TYPE_INTEGER = "Integer";
/**
* 长整型
*/
String TYPE_LONG = "Long";
/**
* 浮点型
*/
String TYPE_DOUBLE = "Double";
/**
* 高精度计算类型
*/
String TYPE_BIGDECIMAL = "BigDecimal";
/**
* 时间类型
*/
String TYPE_DATE = "Date";
/**
* 模糊查询
*/
String QUERY_LIKE = "LIKE";
/**
* 相等查询
*/
String QUERY_EQ = "EQ";
/**
* 需要
*/
String REQUIRE = "1";
}

View File

@ -0,0 +1,217 @@
package org.dromara.generator.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.IoUtil;
import org.dromara.common.core.domain.R;
import org.dromara.common.mybatis.helper.DataBaseHelper;
import org.dromara.common.web.core.BaseController;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.generator.domain.GenTable;
import org.dromara.generator.domain.GenTableColumn;
import org.dromara.generator.service.IGenTableService;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 代码生成 操作处理
*
* @author Lion Li
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/tool/gen")
public class GenController extends BaseController {
private final IGenTableService genTableService;
/**
* 查询代码生成列表
*/
@SaCheckPermission("tool:gen:list")
@GetMapping("/list")
public TableDataInfo<GenTable> genList(GenTable genTable, PageQuery pageQuery) {
return genTableService.selectPageGenTableList(genTable, pageQuery);
}
/**
* 修改代码生成业务
*
* @param tableId 表ID
*/
@SaCheckPermission("tool:gen:query")
@GetMapping(value = "/{tableId}")
public R<Map<String, Object>> getInfo(@PathVariable Long tableId) {
GenTable table = genTableService.selectGenTableById(tableId);
List<GenTable> tables = genTableService.selectGenTableAll();
List<GenTableColumn> list = genTableService.selectGenTableColumnListByTableId(tableId);
Map<String, Object> map = new HashMap<>(3);
map.put("info", table);
map.put("rows", list);
map.put("tables", tables);
return R.ok(map);
}
/**
* 查询数据库列表
*/
@SaCheckPermission("tool:gen:list")
@GetMapping("/db/list")
public TableDataInfo<GenTable> dataList(GenTable genTable, PageQuery pageQuery) {
return genTableService.selectPageDbTableList(genTable, pageQuery);
}
/**
* 查询数据表字段列表
*
* @param tableId 表ID
*/
@SaCheckPermission("tool:gen:list")
@GetMapping(value = "/column/{tableId}")
public TableDataInfo<GenTableColumn> columnList(@PathVariable("tableId") Long tableId) {
TableDataInfo<GenTableColumn> dataInfo = new TableDataInfo<>();
List<GenTableColumn> list = genTableService.selectGenTableColumnListByTableId(tableId);
dataInfo.setRows(list);
dataInfo.setTotal(list.size());
return dataInfo;
}
/**
* 导入表结构(保存)
*
* @param tables 表名串
*/
@SaCheckPermission("tool:gen:import")
@Log(title = "代码生成", businessType = BusinessType.IMPORT)
@PostMapping("/importTable")
public R<Void> importTableSave(String tables, String dataName) {
String[] tableNames = Convert.toStrArray(tables);
// 查询表信息
List<GenTable> tableList = genTableService.selectDbTableListByNames(tableNames, dataName);
genTableService.importGenTable(tableList, dataName);
return R.ok();
}
/**
* 修改保存代码生成业务
*/
@SaCheckPermission("tool:gen:edit")
@Log(title = "代码生成", businessType = BusinessType.UPDATE)
@PutMapping
public R<Void> editSave(@Validated @RequestBody GenTable genTable) {
genTableService.validateEdit(genTable);
genTableService.updateGenTable(genTable);
return R.ok();
}
/**
* 删除代码生成
*
* @param tableIds 表ID串
*/
@SaCheckPermission("tool:gen:remove")
@Log(title = "代码生成", businessType = BusinessType.DELETE)
@DeleteMapping("/{tableIds}")
public R<Void> remove(@PathVariable Long[] tableIds) {
genTableService.deleteGenTableByIds(tableIds);
return R.ok();
}
/**
* 预览代码
*
* @param tableId 表ID
*/
@SaCheckPermission("tool:gen:preview")
@GetMapping("/preview/{tableId}")
public R<Map<String, String>> preview(@PathVariable("tableId") Long tableId) throws IOException {
Map<String, String> dataMap = genTableService.previewCode(tableId);
return R.ok(dataMap);
}
/**
* 生成代码(下载方式)
*
* @param tableId 表ID
*/
@SaCheckPermission("tool:gen:code")
@Log(title = "代码生成", businessType = BusinessType.GENCODE)
@GetMapping("/download/{tableId}")
public void download(HttpServletResponse response, @PathVariable("tableId") Long tableId) throws IOException {
byte[] data = genTableService.downloadCode(tableId);
genCode(response, data);
}
/**
* 生成代码(自定义路径)
*
* @param tableId 表ID
*/
@SaCheckPermission("tool:gen:code")
@Log(title = "代码生成", businessType = BusinessType.GENCODE)
@GetMapping("/genCode/{tableId}")
public R<Void> genCode(@PathVariable("tableId") Long tableId) {
genTableService.generatorCode(tableId);
return R.ok();
}
/**
* 同步数据库
*
* @param tableId 表ID
*/
@SaCheckPermission("tool:gen:edit")
@Log(title = "代码生成", businessType = BusinessType.UPDATE)
@GetMapping("/synchDb/{tableId}")
public R<Void> synchDb(@PathVariable("tableId") Long tableId) {
genTableService.synchDb(tableId);
return R.ok();
}
/**
* 批量生成代码
*
* @param tableIdStr 表ID串
*/
@SaCheckPermission("tool:gen:code")
@Log(title = "代码生成", businessType = BusinessType.GENCODE)
@GetMapping("/batchGenCode")
public void batchGenCode(HttpServletResponse response, String tableIdStr) throws IOException {
String[] tableIds = Convert.toStrArray(tableIdStr);
byte[] data = genTableService.downloadCode(tableIds);
genCode(response, data);
}
/**
* 生成zip文件
*/
private void genCode(HttpServletResponse response, byte[] data) throws IOException {
response.reset();
response.addHeader("Access-Control-Allow-Origin", "*");
response.addHeader("Access-Control-Expose-Headers", "Content-Disposition");
response.setHeader("Content-Disposition", "attachment; filename=\"ruoyi.zip\"");
response.addHeader("Content-Length", "" + data.length);
response.setContentType("application/octet-stream; charset=UTF-8");
IoUtil.write(response.getOutputStream(), false, data);
}
/**
* 查询数据源名称列表
*/
@SaCheckPermission("tool:gen:list")
@GetMapping(value = "/getDataNames")
public R<Object> getCurrentDataSourceNameList(){
return R.ok(DataBaseHelper.getDataSourceNameList());
}
}

View File

@ -0,0 +1,196 @@
package org.dromara.generator.domain;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.generator.constant.GenConstants;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.List;
/**
* 业务表 gen_table
*
* @author Lion Li
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("gen_table")
public class GenTable extends BaseEntity {
/**
* 编号
*/
@TableId(value = "table_id")
private Long tableId;
/**
* 数据源名称
*/
@NotBlank(message = "数据源名称不能为空")
private String dataName;
/**
* 表名称
*/
@NotBlank(message = "表名称不能为空")
private String tableName;
/**
* 表描述
*/
@NotBlank(message = "表描述不能为空")
private String tableComment;
/**
* 关联父表的表名
*/
private String subTableName;
/**
* 本表关联父表的外键名
*/
private String subTableFkName;
/**
* 实体类名称(首字母大写)
*/
@NotBlank(message = "实体类名称不能为空")
private String className;
/**
* 使用的模板crud单表操作 tree树表操作 sub主子表操作
*/
private String tplCategory;
/**
* 生成包路径
*/
@NotBlank(message = "生成包路径不能为空")
private String packageName;
/**
* 生成模块名
*/
@NotBlank(message = "生成模块名不能为空")
private String moduleName;
/**
* 生成业务名
*/
@NotBlank(message = "生成业务名不能为空")
private String businessName;
/**
* 生成功能名
*/
@NotBlank(message = "生成功能名不能为空")
private String functionName;
/**
* 生成作者
*/
@NotBlank(message = "作者不能为空")
private String functionAuthor;
/**
* 生成代码方式0zip压缩包 1自定义路径
*/
private String genType;
/**
* 生成路径(不填默认项目路径)
*/
@TableField(updateStrategy = FieldStrategy.NOT_EMPTY)
private String genPath;
/**
* 主键信息
*/
@TableField(exist = false)
private GenTableColumn pkColumn;
/**
* 表列信息
*/
@Valid
@TableField(exist = false)
private List<GenTableColumn> columns;
/**
* 其它生成选项
*/
private String options;
/**
* 备注
*/
private String remark;
/**
* 树编码字段
*/
@TableField(exist = false)
private String treeCode;
/**
* 树父编码字段
*/
@TableField(exist = false)
private String treeParentCode;
/**
* 树名称字段
*/
@TableField(exist = false)
private String treeName;
/*
* 菜单id列表
*/
@TableField(exist = false)
private List<Long> menuIds;
/**
* 上级菜单ID字段
*/
@TableField(exist = false)
private Long parentMenuId;
/**
* 上级菜单名称字段
*/
@TableField(exist = false)
private String parentMenuName;
public boolean isTree() {
return isTree(this.tplCategory);
}
public static boolean isTree(String tplCategory) {
return tplCategory != null && StringUtils.equals(GenConstants.TPL_TREE, tplCategory);
}
public boolean isCrud() {
return isCrud(this.tplCategory);
}
public static boolean isCrud(String tplCategory) {
return tplCategory != null && StringUtils.equals(GenConstants.TPL_CRUD, tplCategory);
}
public boolean isSuperColumn(String javaField) {
return isSuperColumn(this.tplCategory, javaField);
}
public static boolean isSuperColumn(String tplCategory, String javaField) {
return StringUtils.equalsAnyIgnoreCase(javaField, GenConstants.BASE_ENTITY);
}
}

View File

@ -0,0 +1,222 @@
package org.dromara.generator.domain;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.ibatis.type.JdbcType;
import jakarta.validation.constraints.NotBlank;
/**
* 代码生成业务字段表 gen_table_column
*
* @author Lion Li
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("gen_table_column")
public class GenTableColumn extends BaseEntity {
/**
* 编号
*/
@TableId(value = "column_id")
private Long columnId;
/**
* 归属表编号
*/
private Long tableId;
/**
* 列名称
*/
private String columnName;
/**
* 列描述
*/
@TableField(updateStrategy = FieldStrategy.ALWAYS, jdbcType = JdbcType.VARCHAR)
private String columnComment;
/**
* 列类型
*/
private String columnType;
/**
* JAVA类型
*/
private String javaType;
/**
* JAVA字段名
*/
@NotBlank(message = "Java属性不能为空")
private String javaField;
/**
* 是否主键1是
*/
@TableField(updateStrategy = FieldStrategy.ALWAYS, jdbcType = JdbcType.VARCHAR)
private String isPk;
/**
* 是否自增1是
*/
@TableField(updateStrategy = FieldStrategy.ALWAYS, jdbcType = JdbcType.VARCHAR)
private String isIncrement;
/**
* 是否必填1是
*/
@TableField(updateStrategy = FieldStrategy.ALWAYS, jdbcType = JdbcType.VARCHAR)
private String isRequired;
/**
* 是否为插入字段1是
*/
@TableField(updateStrategy = FieldStrategy.ALWAYS, jdbcType = JdbcType.VARCHAR)
private String isInsert;
/**
* 是否编辑字段1是
*/
@TableField(updateStrategy = FieldStrategy.ALWAYS, jdbcType = JdbcType.VARCHAR)
private String isEdit;
/**
* 是否列表字段1是
*/
@TableField(updateStrategy = FieldStrategy.ALWAYS, jdbcType = JdbcType.VARCHAR)
private String isList;
/**
* 是否查询字段1是
*/
@TableField(updateStrategy = FieldStrategy.ALWAYS, jdbcType = JdbcType.VARCHAR)
private String isQuery;
/**
* 查询方式EQ等于、NE不等于、GT大于、LT小于、LIKE模糊、BETWEEN范围
*/
private String queryType;
/**
* 显示类型input文本框、textarea文本域、select下拉框、checkbox复选框、radio单选框、datetime日期控件、image图片上传控件、upload文件上传控件、editor富文本控件
*/
private String htmlType;
/**
* 字典类型
*/
private String dictType;
/**
* 排序
*/
private Integer sort;
public String getCapJavaField() {
return StringUtils.capitalize(javaField);
}
public boolean isPk() {
return isPk(this.isPk);
}
public boolean isPk(String isPk) {
return isPk != null && StringUtils.equals("1", isPk);
}
public boolean isIncrement() {
return isIncrement(this.isIncrement);
}
public boolean isIncrement(String isIncrement) {
return isIncrement != null && StringUtils.equals("1", isIncrement);
}
public boolean isRequired() {
return isRequired(this.isRequired);
}
public boolean isRequired(String isRequired) {
return isRequired != null && StringUtils.equals("1", isRequired);
}
public boolean isInsert() {
return isInsert(this.isInsert);
}
public boolean isInsert(String isInsert) {
return isInsert != null && StringUtils.equals("1", isInsert);
}
public boolean isEdit() {
return isEdit(this.isEdit);
}
public boolean isEdit(String isEdit) {
return isEdit != null && StringUtils.equals("1", isEdit);
}
public boolean isList() {
return isList(this.isList);
}
public boolean isList(String isList) {
return isList != null && StringUtils.equals("1", isList);
}
public boolean isQuery() {
return isQuery(this.isQuery);
}
public boolean isQuery(String isQuery) {
return isQuery != null && StringUtils.equals("1", isQuery);
}
public boolean isSuperColumn() {
return isSuperColumn(this.javaField);
}
public static boolean isSuperColumn(String javaField) {
return StringUtils.equalsAnyIgnoreCase(javaField,
// BaseEntity
"createBy", "createTime", "updateBy", "updateTime",
// TreeEntity
"parentName", "parentId");
}
public boolean isUsableColumn() {
return isUsableColumn(javaField);
}
public static boolean isUsableColumn(String javaField) {
// isSuperColumn()中的名单用于避免生成多余Domain属性若某些属性在生成页面时需要用到不能忽略则放在此处白名单
return StringUtils.equalsAnyIgnoreCase(javaField, "parentId", "orderNum", "remark");
}
public String readConverterExp() {
String remarks = StringUtils.substringBetween(this.columnComment, "", "");
StringBuffer sb = new StringBuffer();
if (StringUtils.isNotEmpty(remarks)) {
for (String value : remarks.split(" ")) {
if (StringUtils.isNotEmpty(value)) {
Object startStr = value.subSequence(0, 1);
String endStr = value.substring(1);
sb.append(StringUtils.EMPTY).append(startStr).append("=").append(endStr).append(StringUtils.SEPARATOR);
}
}
return sb.deleteCharAt(sb.length() - 1).toString();
} else {
return this.columnComment;
}
}
}

View File

@ -0,0 +1,15 @@
package org.dromara.generator.mapper;
import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.generator.domain.GenTableColumn;
/**
* 业务字段 数据层
*
* @author Lion Li
*/
@InterceptorIgnore(dataPermission = "true", tenantLine = "true")
public interface GenTableColumnMapper extends BaseMapperPlus<GenTableColumn, GenTableColumn> {
}

View File

@ -0,0 +1,51 @@
package org.dromara.generator.mapper;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.generator.domain.GenTable;
import java.util.List;
/**
* 业务 数据层
*
* @author Lion Li
*/
@InterceptorIgnore(dataPermission = "true", tenantLine = "true")
public interface GenTableMapper extends BaseMapperPlus<GenTable, GenTable> {
/**
* 查询所有表信息
*
* @return 表信息集合
*/
List<GenTable> selectGenTableAll();
/**
* 查询表ID业务信息
*
* @param id 业务ID
* @return 业务信息
*/
GenTable selectGenTableById(Long id);
/**
* 查询表名称业务信息
*
* @param tableName 表名称
* @return 业务信息
*/
GenTable selectGenTableByName(String tableName);
/**
* 查询指定数据源下的所有表名列表
*
* @param dataName 数据源名称,用于选择不同的数据源
* @return 当前数据库中的表名列表
*
* @DS("") 使用默认数据源执行查询操作
*/
@DS("")
List<String> selectTableNameList(String dataName);
}

View File

@ -0,0 +1,582 @@
package org.dromara.generator.service;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.anyline.metadata.Column;
import org.anyline.metadata.Table;
import org.anyline.proxy.ServiceProxy;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.dromara.common.core.constant.Constants;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.file.FileUtils;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.generator.constant.GenConstants;
import org.dromara.generator.domain.GenTable;
import org.dromara.generator.domain.GenTableColumn;
import org.dromara.generator.mapper.GenTableColumnMapper;
import org.dromara.generator.mapper.GenTableMapper;
import org.dromara.generator.util.GenUtils;
import org.dromara.generator.util.VelocityInitializer;
import org.dromara.generator.util.VelocityUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
* 业务 服务层实现
*
* @author Lion Li
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class GenTableServiceImpl implements IGenTableService {
private final GenTableMapper baseMapper;
private final GenTableColumnMapper genTableColumnMapper;
private final IdentifierGenerator identifierGenerator;
private static final String[] TABLE_IGNORE = new String[]{"sj_", "flow_", "gen_"};
/**
* 查询业务字段列表
*
* @param tableId 业务字段编号
* @return 业务字段集合
*/
@Override
public List<GenTableColumn> selectGenTableColumnListByTableId(Long tableId) {
return genTableColumnMapper.selectList(new LambdaQueryWrapper<GenTableColumn>()
.eq(GenTableColumn::getTableId, tableId)
.orderByAsc(GenTableColumn::getSort));
}
/**
* 查询业务信息
*
* @param id 业务ID
* @return 业务信息
*/
@Override
public GenTable selectGenTableById(Long id) {
GenTable genTable = baseMapper.selectGenTableById(id);
setTableFromOptions(genTable);
return genTable;
}
@Override
public TableDataInfo<GenTable> selectPageGenTableList(GenTable genTable, PageQuery pageQuery) {
Page<GenTable> page = baseMapper.selectPage(pageQuery.build(), this.buildGenTableQueryWrapper(genTable));
return TableDataInfo.build(page);
}
private QueryWrapper<GenTable> buildGenTableQueryWrapper(GenTable genTable) {
Map<String, Object> params = genTable.getParams();
QueryWrapper<GenTable> wrapper = Wrappers.query();
wrapper
.eq(StringUtils.isNotEmpty(genTable.getDataName()), "data_name", genTable.getDataName())
.like(StringUtils.isNotBlank(genTable.getTableName()), "lower(table_name)", StringUtils.lowerCase(genTable.getTableName()))
.like(StringUtils.isNotBlank(genTable.getTableComment()), "lower(table_comment)", StringUtils.lowerCase(genTable.getTableComment()))
.between(params.get("beginTime") != null && params.get("endTime") != null,
"create_time", params.get("beginTime"), params.get("endTime"))
.orderByDesc("update_time");
return wrapper;
}
/**
* 查询数据库列表
*
* @param genTable 包含查询条件的GenTable对象
* @param pageQuery 包含分页信息的PageQuery对象
* @return 包含分页结果的TableDataInfo对象
*/
@DS("#genTable.dataName")
@Override
public TableDataInfo<GenTable> selectPageDbTableList(GenTable genTable, PageQuery pageQuery) {
// 获取查询条件
String tableName = genTable.getTableName();
String tableComment = genTable.getTableComment();
LinkedHashMap<String, Table<?>> tablesMap = ServiceProxy.metadata().tables();
if (CollUtil.isEmpty(tablesMap)) {
return TableDataInfo.build();
}
List<String> tableNames = baseMapper.selectTableNameList(genTable.getDataName());
String[] tableArrays;
if (CollUtil.isNotEmpty(tableNames)) {
tableArrays = tableNames.toArray(new String[0]);
} else {
tableArrays = new String[0];
}
// 过滤并转换表格数据
List<GenTable> tables = tablesMap.values().stream()
.filter(x -> !StringUtils.startWithAnyIgnoreCase(x.getName(), TABLE_IGNORE))
.filter(x -> {
if (CollUtil.isEmpty(tableNames)) {
return true;
}
return !StringUtils.equalsAnyIgnoreCase(x.getName(), tableArrays);
})
.filter(x -> {
boolean nameMatches = true;
boolean commentMatches = true;
// 进行表名称的模糊查询
if (StringUtils.isNotBlank(tableName)) {
nameMatches = StringUtils.containsIgnoreCase(x.getName(), tableName);
}
// 进行表描述的模糊查询
if (StringUtils.isNotBlank(tableComment)) {
commentMatches = StringUtils.containsIgnoreCase(x.getComment(), tableComment);
}
// 同时匹配名称和描述
return nameMatches && commentMatches;
})
.map(x -> {
GenTable gen = new GenTable();
gen.setTableName(x.getName());
gen.setTableComment(x.getComment());
// postgresql的表元数据没有创建时间这个东西(好奇葩) 只能new Date代替
gen.setCreateTime(ObjectUtil.defaultIfNull(x.getCreateTime(), new Date()));
gen.setUpdateTime(x.getUpdateTime());
return gen;
}).sorted(Comparator.comparing(GenTable::getCreateTime).reversed())
.toList();
IPage<GenTable> page = pageQuery.build();
page.setTotal(tables.size());
// 手动分页 set数据
page.setRecords(CollUtil.page((int) page.getCurrent() - 1, (int) page.getSize(), tables));
return TableDataInfo.build(page);
}
/**
* 查询据库列表
*
* @param tableNames 表名称组
* @param dataName 数据源名称
* @return 数据库表集合
*/
@DS("#dataName")
@Override
public List<GenTable> selectDbTableListByNames(String[] tableNames, String dataName) {
Set<String> tableNameSet = new HashSet<>(List.of(tableNames));
LinkedHashMap<String, Table<?>> tablesMap = ServiceProxy.metadata().tables();
if (CollUtil.isEmpty(tablesMap)) {
return new ArrayList<>();
}
List<Table<?>> tableList = tablesMap.values().stream()
.filter(x -> !StringUtils.startWithAnyIgnoreCase(x.getName(), TABLE_IGNORE))
.filter(x -> tableNameSet.contains(x.getName())).toList();
if (CollUtil.isEmpty(tableList)) {
return new ArrayList<>();
}
return tableList.stream().map(x -> {
GenTable gen = new GenTable();
gen.setDataName(dataName);
gen.setTableName(x.getName());
gen.setTableComment(x.getComment());
gen.setCreateTime(x.getCreateTime());
gen.setUpdateTime(x.getUpdateTime());
return gen;
}).toList();
}
/**
* 查询所有表信息
*
* @return 表信息集合
*/
@Override
public List<GenTable> selectGenTableAll() {
return baseMapper.selectGenTableAll();
}
/**
* 修改业务
*
* @param genTable 业务信息
*/
@Transactional(rollbackFor = Exception.class)
@Override
public void updateGenTable(GenTable genTable) {
String options = JsonUtils.toJsonString(genTable.getParams());
genTable.setOptions(options);
int row = baseMapper.updateById(genTable);
if (row > 0) {
for (GenTableColumn cenTableColumn : genTable.getColumns()) {
genTableColumnMapper.updateById(cenTableColumn);
}
}
}
/**
* 删除业务对象
*
* @param tableIds 需要删除的数据ID
*/
@Transactional(rollbackFor = Exception.class)
@Override
public void deleteGenTableByIds(Long[] tableIds) {
List<Long> ids = Arrays.asList(tableIds);
baseMapper.deleteByIds(ids);
genTableColumnMapper.delete(new LambdaQueryWrapper<GenTableColumn>().in(GenTableColumn::getTableId, ids));
}
/**
* 导入表结构
*
* @param tableList 导入表列表
* @param dataName 数据源名称
*/
@DSTransactional
@Override
public void importGenTable(List<GenTable> tableList, String dataName) {
try {
for (GenTable table : tableList) {
String tableName = table.getTableName();
GenUtils.initTable(table);
table.setDataName(dataName);
int row = baseMapper.insert(table);
if (row > 0) {
// 保存列信息
List<GenTableColumn> genTableColumns = SpringUtils.getAopProxy(this).selectDbTableColumnsByName(tableName, dataName);
List<GenTableColumn> saveColumns = new ArrayList<>();
for (GenTableColumn column : genTableColumns) {
GenUtils.initColumnField(column, table);
saveColumns.add(column);
}
if (CollUtil.isNotEmpty(saveColumns)) {
genTableColumnMapper.insertBatch(saveColumns);
}
}
}
} catch (Exception e) {
throw new ServiceException("导入失败:" + e.getMessage());
}
}
/**
* 根据表名称查询列信息
*
* @param tableName 表名称
* @param dataName 数据源名称
* @return 列信息
*/
@DS("#dataName")
@Override
public List<GenTableColumn> selectDbTableColumnsByName(String tableName, String dataName) {
Table<?> table = ServiceProxy.metadata().table(tableName);
if (ObjectUtil.isNull(table)) {
return new ArrayList<>();
}
LinkedHashMap<String, Column> columns = table.getColumns();
List<GenTableColumn> tableColumns = new ArrayList<>();
columns.forEach((columnName, column) -> {
GenTableColumn tableColumn = new GenTableColumn();
tableColumn.setIsPk(String.valueOf(column.isPrimaryKey()));
tableColumn.setColumnName(column.getName());
tableColumn.setColumnComment(column.getComment());
tableColumn.setColumnType(column.getOriginType().toLowerCase());
tableColumn.setSort(column.getPosition());
tableColumn.setIsRequired(column.isNullable() == 0 ? "1" : "0");
tableColumn.setIsIncrement(column.isAutoIncrement() == -1 ? "0" : "1");
tableColumns.add(tableColumn);
});
return tableColumns;
}
/**
* 预览代码
*
* @param tableId 表编号
* @return 预览数据列表
*/
@Override
public Map<String, String> previewCode(Long tableId) {
Map<String, String> dataMap = new LinkedHashMap<>();
// 查询表信息
GenTable table = baseMapper.selectGenTableById(tableId);
List<Long> menuIds = new ArrayList<>();
for (int i = 0; i < 6; i++) {
menuIds.add(identifierGenerator.nextId(null).longValue());
}
table.setMenuIds(menuIds);
// 设置主键列信息
setPkColumn(table);
VelocityInitializer.initVelocity();
VelocityContext context = VelocityUtils.prepareContext(table);
// 获取模板列表
List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory());
for (String template : templates) {
// 渲染模板
StringWriter sw = new StringWriter();
Template tpl = Velocity.getTemplate(template, Constants.UTF8);
tpl.merge(context, sw);
dataMap.put(template, sw.toString());
}
return dataMap;
}
/**
* 生成代码(下载方式)
*
* @param tableId 表名称
* @return 数据
*/
@Override
public byte[] downloadCode(Long tableId) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ZipOutputStream zip = new ZipOutputStream(outputStream);
generatorCode(tableId, zip);
IoUtil.close(zip);
return outputStream.toByteArray();
}
/**
* 生成代码(自定义路径)
*
* @param tableId 表名称
*/
@Override
public void generatorCode(Long tableId) {
// 查询表信息
GenTable table = baseMapper.selectGenTableById(tableId);
// 设置主键列信息
setPkColumn(table);
VelocityInitializer.initVelocity();
VelocityContext context = VelocityUtils.prepareContext(table);
// 获取模板列表
List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory());
for (String template : templates) {
if (!StringUtils.containsAny(template, "sql.vm", "api.ts.vm", "types.ts.vm", "index.vue.vm", "index-tree.vue.vm")) {
// 渲染模板
StringWriter sw = new StringWriter();
Template tpl = Velocity.getTemplate(template, Constants.UTF8);
tpl.merge(context, sw);
try {
String path = getGenPath(table, template);
FileUtils.writeUtf8String(sw.toString(), path);
} catch (Exception e) {
throw new ServiceException("渲染模板失败,表名:" + table.getTableName());
}
}
}
}
/**
* 同步数据库
*
* @param tableId 表名称
*/
@DSTransactional
@Override
public void synchDb(Long tableId) {
GenTable table = baseMapper.selectGenTableById(tableId);
List<GenTableColumn> tableColumns = table.getColumns();
Map<String, GenTableColumn> tableColumnMap = StreamUtils.toIdentityMap(tableColumns, GenTableColumn::getColumnName);
List<GenTableColumn> dbTableColumns = SpringUtils.getAopProxy(this).selectDbTableColumnsByName(table.getTableName(), table.getDataName());
if (CollUtil.isEmpty(dbTableColumns)) {
throw new ServiceException("同步数据失败,原表结构不存在");
}
List<String> dbTableColumnNames = StreamUtils.toList(dbTableColumns, GenTableColumn::getColumnName);
List<GenTableColumn> saveColumns = new ArrayList<>();
dbTableColumns.forEach(column -> {
GenUtils.initColumnField(column, table);
if (tableColumnMap.containsKey(column.getColumnName())) {
GenTableColumn prevColumn = tableColumnMap.get(column.getColumnName());
column.setColumnId(prevColumn.getColumnId());
if (column.isList()) {
// 如果是列表,继续保留查询方式/字典类型选项
column.setDictType(prevColumn.getDictType());
column.setQueryType(prevColumn.getQueryType());
}
if (StringUtils.isNotEmpty(prevColumn.getIsRequired()) && !column.isPk()
&& (column.isInsert() || column.isEdit())
&& ((column.isUsableColumn()) || (!column.isSuperColumn()))) {
// 如果是(新增/修改&非主键/非忽略及父属性),继续保留必填/显示类型选项
column.setIsRequired(prevColumn.getIsRequired());
column.setHtmlType(prevColumn.getHtmlType());
}
}
saveColumns.add(column);
});
if (CollUtil.isNotEmpty(saveColumns)) {
genTableColumnMapper.insertOrUpdateBatch(saveColumns);
}
List<GenTableColumn> delColumns = StreamUtils.filter(tableColumns, column -> !dbTableColumnNames.contains(column.getColumnName()));
if (CollUtil.isNotEmpty(delColumns)) {
List<Long> ids = StreamUtils.toList(delColumns, GenTableColumn::getColumnId);
if (CollUtil.isNotEmpty(ids)) {
genTableColumnMapper.deleteByIds(ids);
}
}
}
/**
* 批量生成代码(下载方式)
*
* @param tableIds 表ID数组
* @return 数据
*/
@Override
public byte[] downloadCode(String[] tableIds) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ZipOutputStream zip = new ZipOutputStream(outputStream);
for (String tableId : tableIds) {
generatorCode(Long.parseLong(tableId), zip);
}
IoUtil.close(zip);
return outputStream.toByteArray();
}
/**
* 查询表信息并生成代码
*/
private void generatorCode(Long tableId, ZipOutputStream zip) {
// 查询表信息
GenTable table = baseMapper.selectGenTableById(tableId);
List<Long> menuIds = new ArrayList<>();
for (int i = 0; i < 6; i++) {
menuIds.add(identifierGenerator.nextId(null).longValue());
}
table.setMenuIds(menuIds);
// 设置主键列信息
setPkColumn(table);
VelocityInitializer.initVelocity();
VelocityContext context = VelocityUtils.prepareContext(table);
// 获取模板列表
List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory());
for (String template : templates) {
// 渲染模板
StringWriter sw = new StringWriter();
Template tpl = Velocity.getTemplate(template, Constants.UTF8);
tpl.merge(context, sw);
try {
// 添加到zip
zip.putNextEntry(new ZipEntry(VelocityUtils.getFileName(template, table)));
IoUtil.write(zip, StandardCharsets.UTF_8, false, sw.toString());
IoUtil.close(sw);
zip.flush();
zip.closeEntry();
} catch (IOException e) {
log.error("渲染模板失败,表名:" + table.getTableName(), e);
}
}
}
/**
* 修改保存参数校验
*
* @param genTable 业务信息
*/
@Override
public void validateEdit(GenTable genTable) {
if (GenConstants.TPL_TREE.equals(genTable.getTplCategory())) {
String options = JsonUtils.toJsonString(genTable.getParams());
Dict paramsObj = JsonUtils.parseMap(options);
if (StringUtils.isEmpty(paramsObj.getStr(GenConstants.TREE_CODE))) {
throw new ServiceException("树编码字段不能为空");
} else if (StringUtils.isEmpty(paramsObj.getStr(GenConstants.TREE_PARENT_CODE))) {
throw new ServiceException("树父编码字段不能为空");
} else if (StringUtils.isEmpty(paramsObj.getStr(GenConstants.TREE_NAME))) {
throw new ServiceException("树名称字段不能为空");
}
}
}
/**
* 设置主键列信息
*
* @param table 业务表信息
*/
public void setPkColumn(GenTable table) {
for (GenTableColumn column : table.getColumns()) {
if (column.isPk()) {
table.setPkColumn(column);
break;
}
}
if (ObjectUtil.isNull(table.getPkColumn())) {
table.setPkColumn(table.getColumns().get(0));
}
}
/**
* 设置代码生成其他选项值
*
* @param genTable 设置后的生成对象
*/
public void setTableFromOptions(GenTable genTable) {
Dict paramsObj = JsonUtils.parseMap(genTable.getOptions());
if (ObjectUtil.isNotNull(paramsObj)) {
String treeCode = paramsObj.getStr(GenConstants.TREE_CODE);
String treeParentCode = paramsObj.getStr(GenConstants.TREE_PARENT_CODE);
String treeName = paramsObj.getStr(GenConstants.TREE_NAME);
Long parentMenuId = paramsObj.getLong(GenConstants.PARENT_MENU_ID);
String parentMenuName = paramsObj.getStr(GenConstants.PARENT_MENU_NAME);
genTable.setTreeCode(treeCode);
genTable.setTreeParentCode(treeParentCode);
genTable.setTreeName(treeName);
genTable.setParentMenuId(parentMenuId);
genTable.setParentMenuName(parentMenuName);
}
}
/**
* 获取代码生成地址
*
* @param table 业务表信息
* @param template 模板文件路径
* @return 生成地址
*/
public static String getGenPath(GenTable table, String template) {
String genPath = table.getGenPath();
if (StringUtils.equals(genPath, "/")) {
return System.getProperty("user.dir") + File.separator + "src" + File.separator + VelocityUtils.getFileName(template, table);
}
return genPath + File.separator + VelocityUtils.getFileName(template, table);
}
}

View File

@ -0,0 +1,141 @@
package org.dromara.generator.service;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.generator.domain.GenTable;
import org.dromara.generator.domain.GenTableColumn;
import java.util.List;
import java.util.Map;
/**
* 业务 服务层
*
* @author Lion Li
*/
public interface IGenTableService {
/**
* 查询业务字段列表
*
* @param tableId 业务字段编号
* @return 业务字段集合
*/
List<GenTableColumn> selectGenTableColumnListByTableId(Long tableId);
/**
* 查询业务列表
*
* @param genTable 业务信息
* @return 业务集合
*/
TableDataInfo<GenTable> selectPageGenTableList(GenTable genTable, PageQuery pageQuery);
/**
* 查询据库列表
*
* @param genTable 业务信息
* @return 数据库表集合
*/
TableDataInfo<GenTable> selectPageDbTableList(GenTable genTable, PageQuery pageQuery);
/**
* 查询据库列表
*
* @param tableNames 表名称组
* @param dataName 数据源名称
* @return 数据库表集合
*/
List<GenTable> selectDbTableListByNames(String[] tableNames, String dataName);
/**
* 查询所有表信息
*
* @return 表信息集合
*/
List<GenTable> selectGenTableAll();
/**
* 查询业务信息
*
* @param id 业务ID
* @return 业务信息
*/
GenTable selectGenTableById(Long id);
/**
* 修改业务
*
* @param genTable 业务信息
*/
void updateGenTable(GenTable genTable);
/**
* 删除业务信息
*
* @param tableIds 需要删除的表数据ID
*/
void deleteGenTableByIds(Long[] tableIds);
/**
* 导入表结构
*
* @param tableList 导入表列表
* @param dataName 数据源名称
*/
void importGenTable(List<GenTable> tableList, String dataName);
/**
* 根据表名称查询列信息
*
* @param tableName 表名称
* @param dataName 数据源名称
* @return 列信息
*/
List<GenTableColumn> selectDbTableColumnsByName(String tableName, String dataName);
/**
* 预览代码
*
* @param tableId 表编号
* @return 预览数据列表
*/
Map<String, String> previewCode(Long tableId);
/**
* 生成代码(下载方式)
*
* @param tableId 表名称
* @return 数据
*/
byte[] downloadCode(Long tableId);
/**
* 生成代码(自定义路径)
*
* @param tableId 表名称
*/
void generatorCode(Long tableId);
/**
* 同步数据库
*
* @param tableId 表名称
*/
void synchDb(Long tableId);
/**
* 批量生成代码(下载方式)
*
* @param tableIds 表ID数组
* @return 数据
*/
byte[] downloadCode(String[] tableIds);
/**
* 修改保存参数校验
*
* @param genTable 业务信息
*/
void validateEdit(GenTable genTable);
}

View File

@ -0,0 +1,219 @@
package org.dromara.generator.util;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.RegExUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.generator.config.GenConfig;
import org.dromara.generator.constant.GenConstants;
import org.dromara.generator.domain.GenTable;
import org.dromara.generator.domain.GenTableColumn;
import java.util.Arrays;
/**
* 代码生成器 工具类
*
* @author ruoyi
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class GenUtils {
/**
* 初始化表信息
*/
public static void initTable(GenTable genTable) {
genTable.setClassName(convertClassName(genTable.getTableName()));
genTable.setPackageName(GenConfig.getPackageName());
genTable.setModuleName(getModuleName(GenConfig.getPackageName()));
genTable.setBusinessName(getBusinessName(genTable.getTableName()));
genTable.setFunctionName(replaceText(genTable.getTableComment()));
genTable.setFunctionAuthor(GenConfig.getAuthor());
genTable.setCreateTime(null);
genTable.setUpdateTime(null);
}
/**
* 初始化列属性字段
*/
public static void initColumnField(GenTableColumn column, GenTable table) {
String dataType = getDbType(column.getColumnType());
// 统一转小写 避免有些数据库默认大写问题 如果需要特别书写方式 请在实体类增加注解标注别名
String columnName = column.getColumnName().toLowerCase();
column.setTableId(table.getTableId());
column.setCreateTime(null);
column.setUpdateTime(null);
// 设置java字段名
column.setJavaField(StringUtils.toCamelCase(columnName));
// 设置默认类型
column.setJavaType(GenConstants.TYPE_STRING);
column.setQueryType(GenConstants.QUERY_EQ);
if (arraysContains(GenConstants.COLUMNTYPE_STR, dataType) || arraysContains(GenConstants.COLUMNTYPE_TEXT, dataType)) {
// 字符串长度超过500设置为文本域
Integer columnLength = getColumnLength(column.getColumnType());
String htmlType = columnLength >= 500 || arraysContains(GenConstants.COLUMNTYPE_TEXT, dataType) ? GenConstants.HTML_TEXTAREA : GenConstants.HTML_INPUT;
column.setHtmlType(htmlType);
} else if (arraysContains(GenConstants.COLUMNTYPE_TIME, dataType)) {
column.setJavaType(GenConstants.TYPE_DATE);
column.setHtmlType(GenConstants.HTML_DATETIME);
} else if (arraysContains(GenConstants.COLUMNTYPE_NUMBER, dataType)) {
column.setHtmlType(GenConstants.HTML_INPUT);
// 数据库的数字字段与java不匹配 且很多数据库的数字字段很模糊 例如oracle只有number没有细分
// 所以默认数字类型全为Long可在界面上自行编辑想要的类型 有什么特殊需求也可以在这里特殊处理
column.setJavaType(GenConstants.TYPE_LONG);
}
// BO对象 默认插入勾选
if (!arraysContains(GenConstants.COLUMNNAME_NOT_ADD, columnName) && !column.isPk()) {
column.setIsInsert(GenConstants.REQUIRE);
}
// BO对象 默认编辑勾选
if (!arraysContains(GenConstants.COLUMNNAME_NOT_EDIT, columnName)) {
column.setIsEdit(GenConstants.REQUIRE);
}
// VO对象 默认返回勾选
if (!arraysContains(GenConstants.COLUMNNAME_NOT_LIST, columnName)) {
column.setIsList(GenConstants.REQUIRE);
}
// BO对象 默认查询勾选
if (!arraysContains(GenConstants.COLUMNNAME_NOT_QUERY, columnName) && !column.isPk()) {
column.setIsQuery(GenConstants.REQUIRE);
}
// 查询字段类型
if (StringUtils.endsWithIgnoreCase(columnName, "name")) {
column.setQueryType(GenConstants.QUERY_LIKE);
}
// 状态字段设置单选框
if (StringUtils.endsWithIgnoreCase(columnName, "status")) {
column.setHtmlType(GenConstants.HTML_RADIO);
}
// 类型&性别字段设置下拉框
else if (StringUtils.endsWithIgnoreCase(columnName, "type")
|| StringUtils.endsWithIgnoreCase(columnName, "sex")) {
column.setHtmlType(GenConstants.HTML_SELECT);
}
// 图片字段设置图片上传控件
else if (StringUtils.endsWithIgnoreCase(columnName, "image")) {
column.setHtmlType(GenConstants.HTML_IMAGE_UPLOAD);
}
// 文件字段设置文件上传控件
else if (StringUtils.endsWithIgnoreCase(columnName, "file")) {
column.setHtmlType(GenConstants.HTML_FILE_UPLOAD);
}
// 内容字段设置富文本控件
else if (StringUtils.endsWithIgnoreCase(columnName, "content")) {
column.setHtmlType(GenConstants.HTML_EDITOR);
}
}
/**
* 校验数组是否包含指定值
*
* @param arr 数组
* @param targetValue 值
* @return 是否包含
*/
public static boolean arraysContains(String[] arr, String targetValue) {
return Arrays.asList(arr).contains(targetValue);
}
/**
* 获取模块名
*
* @param packageName 包名
* @return 模块名
*/
public static String getModuleName(String packageName) {
int lastIndex = packageName.lastIndexOf(".");
int nameLength = packageName.length();
return StringUtils.substring(packageName, lastIndex + 1, nameLength);
}
/**
* 获取业务名
*
* @param tableName 表名
* @return 业务名
*/
public static String getBusinessName(String tableName) {
int firstIndex = tableName.indexOf("_");
int nameLength = tableName.length();
String businessName = StringUtils.substring(tableName, firstIndex + 1, nameLength);
businessName = StringUtils.toCamelCase(businessName);
return businessName;
}
/**
* 表名转换成Java类名
*
* @param tableName 表名称
* @return 类名
*/
public static String convertClassName(String tableName) {
boolean autoRemovePre = GenConfig.getAutoRemovePre();
String tablePrefix = GenConfig.getTablePrefix();
if (autoRemovePre && StringUtils.isNotEmpty(tablePrefix)) {
String[] searchList = StringUtils.split(tablePrefix, StringUtils.SEPARATOR);
tableName = replaceFirst(tableName, searchList);
}
return StringUtils.convertToCamelCase(tableName);
}
/**
* 批量替换前缀
*
* @param replacementm 替换值
* @param searchList 替换列表
*/
public static String replaceFirst(String replacementm, String[] searchList) {
String text = replacementm;
for (String searchString : searchList) {
if (replacementm.startsWith(searchString)) {
text = replacementm.replaceFirst(searchString, StringUtils.EMPTY);
break;
}
}
return text;
}
/**
* 关键字替换
*
* @param text 需要被替换的名字
* @return 替换后的名字
*/
public static String replaceText(String text) {
return RegExUtils.replaceAll(text, "(?:表|若依)", "");
}
/**
* 获取数据库类型字段
*
* @param columnType 列类型
* @return 截取后的列类型
*/
public static String getDbType(String columnType) {
if (StringUtils.indexOf(columnType, "(") > 0) {
return StringUtils.substringBefore(columnType, "(");
} else {
return columnType;
}
}
/**
* 获取字段长度
*
* @param columnType 列类型
* @return 截取后的列类型
*/
public static Integer getColumnLength(String columnType) {
if (StringUtils.indexOf(columnType, "(") > 0) {
String length = StringUtils.substringBetween(columnType, "(", ")");
return Integer.valueOf(length);
} else {
return 0;
}
}
}

View File

@ -0,0 +1,35 @@
package org.dromara.generator.util;
import org.dromara.common.core.constant.Constants;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.apache.velocity.app.Velocity;
import java.util.Properties;
/**
* VelocityEngine工厂
*
* @author ruoyi
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class VelocityInitializer {
/**
* 初始化vm方法
*/
public static void initVelocity() {
Properties p = new Properties();
try {
// 加载classpath目录下的vm文件
p.setProperty("resource.loader.file.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
// 定义字符集
p.setProperty(Velocity.INPUT_ENCODING, Constants.UTF8);
// 初始化Velocity引擎指定配置Properties
Velocity.init(p);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

View File

@ -0,0 +1,341 @@
package org.dromara.generator.util;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.Dict;
import org.dromara.generator.constant.GenConstants;
import org.dromara.common.core.utils.DateUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.mybatis.helper.DataBaseHelper;
import org.dromara.generator.domain.GenTable;
import org.dromara.generator.domain.GenTableColumn;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.apache.velocity.VelocityContext;
import java.util.*;
/**
* 模板处理工具类
*
* @author ruoyi
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class VelocityUtils {
/**
* 项目空间路径
*/
private static final String PROJECT_PATH = "main/java";
/**
* mybatis空间路径
*/
private static final String MYBATIS_PATH = "main/resources/mapper";
/**
* 默认上级菜单,系统工具
*/
private static final String DEFAULT_PARENT_MENU_ID = "3";
/**
* 设置模板变量信息
*
* @return 模板列表
*/
public static VelocityContext prepareContext(GenTable genTable) {
String moduleName = genTable.getModuleName();
String businessName = genTable.getBusinessName();
String packageName = genTable.getPackageName();
String tplCategory = genTable.getTplCategory();
String functionName = genTable.getFunctionName();
VelocityContext velocityContext = new VelocityContext();
velocityContext.put("tplCategory", genTable.getTplCategory());
velocityContext.put("tableName", genTable.getTableName());
velocityContext.put("functionName", StringUtils.isNotEmpty(functionName) ? functionName : "【请填写功能名称】");
velocityContext.put("ClassName", genTable.getClassName());
velocityContext.put("className", StringUtils.uncapitalize(genTable.getClassName()));
velocityContext.put("moduleName", genTable.getModuleName());
velocityContext.put("BusinessName", StringUtils.capitalize(genTable.getBusinessName()));
velocityContext.put("businessName", genTable.getBusinessName());
velocityContext.put("basePackage", getPackagePrefix(packageName));
velocityContext.put("packageName", packageName);
velocityContext.put("author", genTable.getFunctionAuthor());
velocityContext.put("datetime", DateUtils.getDate());
velocityContext.put("pkColumn", genTable.getPkColumn());
velocityContext.put("importList", getImportList(genTable));
velocityContext.put("permissionPrefix", getPermissionPrefix(moduleName, businessName));
velocityContext.put("columns", genTable.getColumns());
velocityContext.put("table", genTable);
velocityContext.put("dicts", getDicts(genTable));
setMenuVelocityContext(velocityContext, genTable);
if (GenConstants.TPL_TREE.equals(tplCategory)) {
setTreeVelocityContext(velocityContext, genTable);
}
return velocityContext;
}
public static void setMenuVelocityContext(VelocityContext context, GenTable genTable) {
String options = genTable.getOptions();
Dict paramsObj = JsonUtils.parseMap(options);
String parentMenuId = getParentMenuId(paramsObj);
context.put("parentMenuId", parentMenuId);
}
public static void setTreeVelocityContext(VelocityContext context, GenTable genTable) {
String options = genTable.getOptions();
Dict paramsObj = JsonUtils.parseMap(options);
String treeCode = getTreecode(paramsObj);
String treeParentCode = getTreeParentCode(paramsObj);
String treeName = getTreeName(paramsObj);
context.put("treeCode", treeCode);
context.put("treeParentCode", treeParentCode);
context.put("treeName", treeName);
context.put("expandColumn", getExpandColumn(genTable));
if (paramsObj.containsKey(GenConstants.TREE_PARENT_CODE)) {
context.put("tree_parent_code", paramsObj.get(GenConstants.TREE_PARENT_CODE));
}
if (paramsObj.containsKey(GenConstants.TREE_NAME)) {
context.put("tree_name", paramsObj.get(GenConstants.TREE_NAME));
}
}
/**
* 获取模板信息
*
* @return 模板列表
*/
public static List<String> getTemplateList(String tplCategory) {
List<String> templates = new ArrayList<>();
templates.add("vm/java/domain.java.vm");
templates.add("vm/java/vo.java.vm");
templates.add("vm/java/bo.java.vm");
templates.add("vm/java/mapper.java.vm");
templates.add("vm/java/service.java.vm");
templates.add("vm/java/serviceImpl.java.vm");
templates.add("vm/java/controller.java.vm");
templates.add("vm/xml/mapper.xml.vm");
if (DataBaseHelper.isOracle()) {
templates.add("vm/sql/oracle/sql.vm");
} else if (DataBaseHelper.isPostgerSql()) {
templates.add("vm/sql/postgres/sql.vm");
} else if (DataBaseHelper.isSqlServer()) {
templates.add("vm/sql/sqlserver/sql.vm");
} else {
templates.add("vm/sql/sql.vm");
}
templates.add("vm/ts/api.ts.vm");
templates.add("vm/ts/types.ts.vm");
if (GenConstants.TPL_CRUD.equals(tplCategory)) {
templates.add("vm/vue/index.vue.vm");
} else if (GenConstants.TPL_TREE.equals(tplCategory)) {
templates.add("vm/vue/index-tree.vue.vm");
}
return templates;
}
/**
* 获取文件名
*/
public static String getFileName(String template, GenTable genTable) {
// 文件名称
String fileName = "";
// 包路径
String packageName = genTable.getPackageName();
// 模块名
String moduleName = genTable.getModuleName();
// 大写类名
String className = genTable.getClassName();
// 业务名称
String businessName = genTable.getBusinessName();
String javaPath = PROJECT_PATH + "/" + StringUtils.replace(packageName, ".", "/");
String mybatisPath = MYBATIS_PATH + "/" + moduleName;
String vuePath = "vue";
if (template.contains("domain.java.vm")) {
fileName = StringUtils.format("{}/domain/{}.java", javaPath, className);
}
if (template.contains("vo.java.vm")) {
fileName = StringUtils.format("{}/domain/vo/{}Vo.java", javaPath, className);
}
if (template.contains("bo.java.vm")) {
fileName = StringUtils.format("{}/domain/bo/{}Bo.java", javaPath, className);
}
if (template.contains("mapper.java.vm")) {
fileName = StringUtils.format("{}/mapper/{}Mapper.java", javaPath, className);
} else if (template.contains("service.java.vm")) {
fileName = StringUtils.format("{}/service/I{}Service.java", javaPath, className);
} else if (template.contains("serviceImpl.java.vm")) {
fileName = StringUtils.format("{}/service/impl/{}ServiceImpl.java", javaPath, className);
} else if (template.contains("controller.java.vm")) {
fileName = StringUtils.format("{}/controller/{}Controller.java", javaPath, className);
} else if (template.contains("mapper.xml.vm")) {
fileName = StringUtils.format("{}/{}Mapper.xml", mybatisPath, className);
} else if (template.contains("sql.vm")) {
fileName = businessName + "Menu.sql";
} else if (template.contains("api.ts.vm")) {
fileName = StringUtils.format("{}/api/{}/{}/index.ts", vuePath, moduleName, businessName);
} else if (template.contains("types.ts.vm")) {
fileName = StringUtils.format("{}/api/{}/{}/types.ts", vuePath, moduleName, businessName);
} else if (template.contains("index.vue.vm")) {
fileName = StringUtils.format("{}/views/{}/{}/index.vue", vuePath, moduleName, businessName);
} else if (template.contains("index-tree.vue.vm")) {
fileName = StringUtils.format("{}/views/{}/{}/index.vue", vuePath, moduleName, businessName);
}
return fileName;
}
/**
* 获取包前缀
*
* @param packageName 包名称
* @return 包前缀名称
*/
public static String getPackagePrefix(String packageName) {
int lastIndex = packageName.lastIndexOf(".");
return StringUtils.substring(packageName, 0, lastIndex);
}
/**
* 根据列类型获取导入包
*
* @param genTable 业务表对象
* @return 返回需要导入的包列表
*/
public static HashSet<String> getImportList(GenTable genTable) {
List<GenTableColumn> columns = genTable.getColumns();
HashSet<String> importList = new HashSet<>();
for (GenTableColumn column : columns) {
if (!column.isSuperColumn() && GenConstants.TYPE_DATE.equals(column.getJavaType())) {
importList.add("java.util.Date");
importList.add("com.fasterxml.jackson.annotation.JsonFormat");
} else if (!column.isSuperColumn() && GenConstants.TYPE_BIGDECIMAL.equals(column.getJavaType())) {
importList.add("java.math.BigDecimal");
} else if (!column.isSuperColumn() && "imageUpload".equals(column.getHtmlType())) {
importList.add("org.dromara.common.translation.annotation.Translation");
importList.add("org.dromara.common.translation.constant.TransConstant");
}
}
return importList;
}
/**
* 根据列类型获取字典组
*
* @param genTable 业务表对象
* @return 返回字典组
*/
public static String getDicts(GenTable genTable) {
List<GenTableColumn> columns = genTable.getColumns();
Set<String> dicts = new HashSet<>();
addDicts(dicts, columns);
return StringUtils.join(dicts, ", ");
}
/**
* 添加字典列表
*
* @param dicts 字典列表
* @param columns 列集合
*/
public static void addDicts(Set<String> dicts, List<GenTableColumn> columns) {
for (GenTableColumn column : columns) {
if (!column.isSuperColumn() && StringUtils.isNotEmpty(column.getDictType()) && StringUtils.equalsAny(
column.getHtmlType(),
new String[] { GenConstants.HTML_SELECT, GenConstants.HTML_RADIO, GenConstants.HTML_CHECKBOX })) {
dicts.add("'" + column.getDictType() + "'");
}
}
}
/**
* 获取权限前缀
*
* @param moduleName 模块名称
* @param businessName 业务名称
* @return 返回权限前缀
*/
public static String getPermissionPrefix(String moduleName, String businessName) {
return StringUtils.format("{}:{}", moduleName, businessName);
}
/**
* 获取上级菜单ID字段
*
* @param paramsObj 生成其他选项
* @return 上级菜单ID字段
*/
public static String getParentMenuId(Dict paramsObj) {
if (CollUtil.isNotEmpty(paramsObj) && paramsObj.containsKey(GenConstants.PARENT_MENU_ID)
&& StringUtils.isNotEmpty(paramsObj.getStr(GenConstants.PARENT_MENU_ID))) {
return paramsObj.getStr(GenConstants.PARENT_MENU_ID);
}
return DEFAULT_PARENT_MENU_ID;
}
/**
* 获取树编码
*
* @param paramsObj 生成其他选项
* @return 树编码
*/
public static String getTreecode(Map<String, Object> paramsObj) {
if (CollUtil.isNotEmpty(paramsObj) && paramsObj.containsKey(GenConstants.TREE_CODE)) {
return StringUtils.toCamelCase(Convert.toStr(paramsObj.get(GenConstants.TREE_CODE)));
}
return StringUtils.EMPTY;
}
/**
* 获取树父编码
*
* @param paramsObj 生成其他选项
* @return 树父编码
*/
public static String getTreeParentCode(Dict paramsObj) {
if (CollUtil.isNotEmpty(paramsObj) && paramsObj.containsKey(GenConstants.TREE_PARENT_CODE)) {
return StringUtils.toCamelCase(paramsObj.getStr(GenConstants.TREE_PARENT_CODE));
}
return StringUtils.EMPTY;
}
/**
* 获取树名称
*
* @param paramsObj 生成其他选项
* @return 树名称
*/
public static String getTreeName(Dict paramsObj) {
if (CollUtil.isNotEmpty(paramsObj) && paramsObj.containsKey(GenConstants.TREE_NAME)) {
return StringUtils.toCamelCase(paramsObj.getStr(GenConstants.TREE_NAME));
}
return StringUtils.EMPTY;
}
/**
* 获取需要在哪一列上面显示展开按钮
*
* @param genTable 业务表对象
* @return 展开按钮列序号
*/
public static int getExpandColumn(GenTable genTable) {
String options = genTable.getOptions();
Dict paramsObj = JsonUtils.parseMap(options);
String treeName = paramsObj.getStr(GenConstants.TREE_NAME);
int num = 0;
for (GenTableColumn column : genTable.getColumns()) {
if (column.isList()) {
num++;
String columnName = column.getColumnName();
if (columnName.equals(treeName)) {
break;
}
}
}
return num;
}
}

View File

@ -0,0 +1,10 @@
# 代码生成
gen:
# 作者
author: Lion Li
# 默认生成包路径 system 需改成自己的模块名称 如 system monitor tool
packageName: org.dromara.system
# 自动去除表前缀默认是false
autoRemovePre: false
# 表前缀(生成类名不会包含表前缀,多个用逗号分隔)
tablePrefix: sys_

View File

@ -0,0 +1,10 @@
<?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="org.dromara.generator.mapper.GenTableColumnMapper">
<resultMap type="org.dromara.generator.domain.GenTableColumn" id="GenTableColumnResult">
</resultMap>
</mapper>

View File

@ -0,0 +1,42 @@
<?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="org.dromara.generator.mapper.GenTableMapper">
<!-- 多结构嵌套自动映射需带上每个实体的主键id 否则映射会失败 -->
<resultMap type="org.dromara.generator.domain.GenTable" id="GenTableResult">
<id property="tableId" column="table_id" />
<collection property="columns" javaType="java.util.List" resultMap="GenTableColumnResult" />
</resultMap>
<resultMap type="org.dromara.generator.domain.GenTableColumn" id="GenTableColumnResult">
<id property="columnId" column="column_id"/>
</resultMap>
<sql id="genSelect">
SELECT t.table_id, t.data_name, t.table_name, t.table_comment, t.sub_table_name, t.sub_table_fk_name, t.class_name, t.tpl_category, t.package_name, t.module_name, t.business_name, t.function_name, t.function_author, t.gen_type, t.gen_path, t.options, t.remark,
c.column_id, c.column_name, c.column_comment, c.column_type, c.java_type, c.java_field, c.is_pk, c.is_increment, c.is_required, c.is_insert, c.is_edit, c.is_list, c.is_query, c.query_type, c.html_type, c.dict_type, c.sort
FROM gen_table t
LEFT JOIN gen_table_column c ON t.table_id = c.table_id
</sql>
<select id="selectGenTableById" parameterType="Long" resultMap="GenTableResult">
<include refid="genSelect"/>
where t.table_id = #{tableId} order by c.sort
</select>
<select id="selectGenTableByName" parameterType="String" resultMap="GenTableResult">
<include refid="genSelect"/>
where t.table_name = #{tableName} order by c.sort
</select>
<select id="selectGenTableAll" parameterType="String" resultMap="GenTableResult">
<include refid="genSelect"/>
order by c.sort
</select>
<select id="selectTableNameList" resultType="java.lang.String">
select table_name from gen_table where data_name = #{dataName,jdbcType=VARCHAR}
</select>
</mapper>

View File

@ -0,0 +1,3 @@
java包使用 `.` 分割 resource 目录使用 `/` 分割
<br>
此文件目的 防止文件夹粘连找不到 `xml` 文件

View File

@ -0,0 +1,50 @@
package ${packageName}.domain.bo;
import ${packageName}.domain.${ClassName};
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import jakarta.validation.constraints.*;
#foreach ($import in $importList)
import ${import};
#end
/**
* ${functionName}业务对象 ${tableName}
*
* @author ${author}
* @date ${datetime}
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = ${ClassName}.class, reverseConvertGenerate = false)
public class ${ClassName}Bo extends BaseEntity {
#foreach ($column in $columns)
#if(!$table.isSuperColumn($column.javaField) && ($column.query || $column.insert || $column.edit))
/**
* $column.columnComment
*/
#if($column.insert && $column.edit)
#set($Group="AddGroup.class, EditGroup.class")
#elseif($column.insert)
#set($Group="AddGroup.class")
#elseif($column.edit)
#set($Group="EditGroup.class")
#end
#if($column.required)
#if($column.javaType == 'String')
@NotBlank(message = "$column.columnComment不能为空", groups = { $Group })
#else
@NotNull(message = "$column.columnComment不能为空", groups = { $Group })
#end
#end
private $column.javaType $column.javaField;
#end
#end
}

View File

@ -0,0 +1,115 @@
package ${packageName}.controller;
import java.util.List;
import lombok.RequiredArgsConstructor;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.*;
import cn.dev33.satoken.annotation.SaCheckPermission;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.annotation.Validated;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.web.core.BaseController;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.excel.utils.ExcelUtil;
import ${packageName}.domain.vo.${ClassName}Vo;
import ${packageName}.domain.bo.${ClassName}Bo;
import ${packageName}.service.I${ClassName}Service;
#if($table.crud)
import org.dromara.common.mybatis.core.page.TableDataInfo;
#elseif($table.tree)
#end
/**
* ${functionName}
*
* @author ${author}
* @date ${datetime}
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/${moduleName}/${businessName}")
public class ${ClassName}Controller extends BaseController {
private final I${ClassName}Service ${className}Service;
/**
* 查询${functionName}列表
*/
@SaCheckPermission("${permissionPrefix}:list")
@GetMapping("/list")
#if($table.crud)
public TableDataInfo<${ClassName}Vo> list(${ClassName}Bo bo, PageQuery pageQuery) {
return ${className}Service.queryPageList(bo, pageQuery);
}
#elseif($table.tree)
public R<List<${ClassName}Vo>> list(${ClassName}Bo bo) {
List<${ClassName}Vo> list = ${className}Service.queryList(bo);
return R.ok(list);
}
#end
/**
* 导出${functionName}列表
*/
@SaCheckPermission("${permissionPrefix}:export")
@Log(title = "${functionName}", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(${ClassName}Bo bo, HttpServletResponse response) {
List<${ClassName}Vo> list = ${className}Service.queryList(bo);
ExcelUtil.exportExcel(list, "${functionName}", ${ClassName}Vo.class, response);
}
/**
* 获取${functionName}详细信息
*
* @param ${pkColumn.javaField} 主键
*/
@SaCheckPermission("${permissionPrefix}:query")
@GetMapping("/{${pkColumn.javaField}}")
public R<${ClassName}Vo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable ${pkColumn.javaType} ${pkColumn.javaField}) {
return R.ok(${className}Service.queryById(${pkColumn.javaField}));
}
/**
* 新增${functionName}
*/
@SaCheckPermission("${permissionPrefix}:add")
@Log(title = "${functionName}", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody ${ClassName}Bo bo) {
return toAjax(${className}Service.insertByBo(bo));
}
/**
* 修改${functionName}
*/
@SaCheckPermission("${permissionPrefix}:edit")
@Log(title = "${functionName}", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody ${ClassName}Bo bo) {
return toAjax(${className}Service.updateByBo(bo));
}
/**
* 删除${functionName}
*
* @param ${pkColumn.javaField}s 主键串
*/
@SaCheckPermission("${permissionPrefix}:remove")
@Log(title = "${functionName}", businessType = BusinessType.DELETE)
@DeleteMapping("/{${pkColumn.javaField}s}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable ${pkColumn.javaType}[] ${pkColumn.javaField}s) {
return toAjax(${className}Service.deleteWithValidByIds(List.of(${pkColumn.javaField}s), true));
}
}

View File

@ -0,0 +1,60 @@
package ${packageName}.domain;
#foreach ($column in $columns)
#if($column.javaField=='tenantId')
#set($IsTenant=1)
#end
#end
#if($IsTenant==1)
import org.dromara.common.tenant.core.TenantEntity;
#else
import org.dromara.common.mybatis.core.domain.BaseEntity;
#end
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
#foreach ($import in $importList)
import ${import};
#end
import java.io.Serial;
/**
* ${functionName}对象 ${tableName}
*
* @author ${author}
* @date ${datetime}
*/
#if($IsTenant==1)
#set($Entity="TenantEntity")
#else
#set($Entity="BaseEntity")
#end
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("${tableName}")
public class ${ClassName} extends ${Entity} {
@Serial
private static final long serialVersionUID = 1L;
#foreach ($column in $columns)
#if(!$table.isSuperColumn($column.javaField))
/**
* $column.columnComment
*/
#if($column.javaField=='delFlag')
@TableLogic
#end
#if($column.javaField=='version')
@Version
#end
#if($column.isPk==1)
@TableId(value = "$column.columnName")
#end
private $column.javaType $column.javaField;
#end
#end
}

View File

@ -0,0 +1,15 @@
package ${packageName}.mapper;
import ${packageName}.domain.${ClassName};
import ${packageName}.domain.vo.${ClassName}Vo;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
/**
* ${functionName}Mapper接口
*
* @author ${author}
* @date ${datetime}
*/
public interface ${ClassName}Mapper extends BaseMapperPlus<${ClassName}, ${ClassName}Vo> {
}

View File

@ -0,0 +1,72 @@
package ${packageName}.service;
import ${packageName}.domain.vo.${ClassName}Vo;
import ${packageName}.domain.bo.${ClassName}Bo;
#if($table.crud)
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
#end
import java.util.Collection;
import java.util.List;
/**
* ${functionName}Service接口
*
* @author ${author}
* @date ${datetime}
*/
public interface I${ClassName}Service {
/**
* 查询${functionName}
*
* @param ${pkColumn.javaField} 主键
* @return ${functionName}
*/
${ClassName}Vo queryById(${pkColumn.javaType} ${pkColumn.javaField});
#if($table.crud)
/**
* 分页查询${functionName}列表
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return ${functionName}分页列表
*/
TableDataInfo<${ClassName}Vo> queryPageList(${ClassName}Bo bo, PageQuery pageQuery);
#end
/**
* 查询符合条件的${functionName}列表
*
* @param bo 查询条件
* @return ${functionName}列表
*/
List<${ClassName}Vo> queryList(${ClassName}Bo bo);
/**
* 新增${functionName}
*
* @param bo ${functionName}
* @return 是否新增成功
*/
Boolean insertByBo(${ClassName}Bo bo);
/**
* 修改${functionName}
*
* @param bo ${functionName}
* @return 是否修改成功
*/
Boolean updateByBo(${ClassName}Bo bo);
/**
* 校验并批量删除${functionName}信息
*
* @param ids 待删除的主键集合
* @param isValid 是否进行有效性校验
* @return 是否删除成功
*/
Boolean deleteWithValidByIds(Collection<${pkColumn.javaType}> ids, Boolean isValid);
}

View File

@ -0,0 +1,158 @@
package ${packageName}.service.impl;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
#if($table.crud)
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
#end
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import ${packageName}.domain.bo.${ClassName}Bo;
import ${packageName}.domain.vo.${ClassName}Vo;
import ${packageName}.domain.${ClassName};
import ${packageName}.mapper.${ClassName}Mapper;
import ${packageName}.service.I${ClassName}Service;
import java.util.List;
import java.util.Map;
import java.util.Collection;
/**
* ${functionName}Service业务层处理
*
* @author ${author}
* @date ${datetime}
*/
@RequiredArgsConstructor
@Service
public class ${ClassName}ServiceImpl implements I${ClassName}Service {
private final ${ClassName}Mapper baseMapper;
/**
* 查询${functionName}
*
* @param ${pkColumn.javaField} 主键
* @return ${functionName}
*/
@Override
public ${ClassName}Vo queryById(${pkColumn.javaType} ${pkColumn.javaField}){
return baseMapper.selectVoById(${pkColumn.javaField});
}
#if($table.crud)
/**
* 分页查询${functionName}列表
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return ${functionName}分页列表
*/
@Override
public TableDataInfo<${ClassName}Vo> queryPageList(${ClassName}Bo bo, PageQuery pageQuery) {
LambdaQueryWrapper<${ClassName}> lqw = buildQueryWrapper(bo);
Page<${ClassName}Vo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
return TableDataInfo.build(result);
}
#end
/**
* 查询符合条件的${functionName}列表
*
* @param bo 查询条件
* @return ${functionName}列表
*/
@Override
public List<${ClassName}Vo> queryList(${ClassName}Bo bo) {
LambdaQueryWrapper<${ClassName}> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw);
}
private LambdaQueryWrapper<${ClassName}> buildQueryWrapper(${ClassName}Bo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<${ClassName}> lqw = Wrappers.lambdaQuery();
#foreach($column in $columns)
#if($column.query)
#set($queryType=$column.queryType)
#set($javaField=$column.javaField)
#set($javaType=$column.javaType)
#set($columnName=$column.columnName)
#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
#set($mpMethod=$column.queryType.toLowerCase())
#if($queryType != 'BETWEEN')
#if($javaType == 'String')
#set($condition='StringUtils.isNotBlank(bo.get'+$AttrName+'())')
#else
#set($condition='bo.get'+$AttrName+'() != null')
#end
lqw.$mpMethod($condition, ${ClassName}::get$AttrName, bo.get$AttrName());
#else
lqw.between(params.get("begin$AttrName") != null && params.get("end$AttrName") != null,
${ClassName}::get$AttrName ,params.get("begin$AttrName"), params.get("end$AttrName"));
#end
#end
#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
#if($column.isPk==1)
lqw.orderByAsc(${ClassName}::get$AttrName);
#end
#end
return lqw;
}
/**
* 新增${functionName}
*
* @param bo ${functionName}
* @return 是否新增成功
*/
@Override
public Boolean insertByBo(${ClassName}Bo bo) {
${ClassName} add = MapstructUtils.convert(bo, ${ClassName}.class);
validEntityBeforeSave(add);
boolean flag = baseMapper.insert(add) > 0;
#set($pk=$pkColumn.javaField.substring(0,1).toUpperCase() + ${pkColumn.javaField.substring(1)})
if (flag) {
bo.set$pk(add.get$pk());
}
return flag;
}
/**
* 修改${functionName}
*
* @param bo ${functionName}
* @return 是否修改成功
*/
@Override
public Boolean updateByBo(${ClassName}Bo bo) {
${ClassName} update = MapstructUtils.convert(bo, ${ClassName}.class);
validEntityBeforeSave(update);
return baseMapper.updateById(update) > 0;
}
/**
* 保存前的数据校验
*/
private void validEntityBeforeSave(${ClassName} entity){
//TODO 做一些数据校验,如唯一约束
}
/**
* 校验并批量删除${functionName}信息
*
* @param ids 待删除的主键集合
* @param isValid 是否进行有效性校验
* @return 是否删除成功
*/
@Override
public Boolean deleteWithValidByIds(Collection<${pkColumn.javaType}> ids, Boolean isValid) {
if(isValid){
//TODO 做一些业务上的校验,判断是否需要校验
}
return baseMapper.deleteByIds(ids) > 0;
}
}

View File

@ -0,0 +1,66 @@
package ${packageName}.domain.vo;
#foreach ($import in $importList)
import ${import};
#end
import ${packageName}.domain.${ClassName};
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import org.dromara.common.excel.annotation.ExcelDictFormat;
import org.dromara.common.excel.convert.ExcelDictConvert;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* ${functionName}视图对象 ${tableName}
*
* @author ${author}
* @date ${datetime}
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = ${ClassName}.class)
public class ${ClassName}Vo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
#foreach ($column in $columns)
#if($column.list)
/**
* $column.columnComment
*/
#set($parentheseIndex=$column.columnComment.indexOf(""))
#if($parentheseIndex != -1)
#set($comment=$column.columnComment.substring(0, $parentheseIndex))
#else
#set($comment=$column.columnComment)
#end
#if(${column.dictType} && ${column.dictType} != '')
@ExcelProperty(value = "${comment}", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "${column.dictType}")
#elseif($parentheseIndex != -1)
@ExcelProperty(value = "${comment}", converter = ExcelDictConvert.class)
@ExcelDictFormat(readConverterExp = "$column.readConverterExp()")
#else
@ExcelProperty(value = "${comment}")
#end
private $column.javaType $column.javaField;
#if($column.htmlType == "imageUpload")
/**
* ${column.columnComment}Url
*/
@Translation(type = TransConstant.OSS_ID_TO_URL, mapper = "${column.javaField}")
private String ${column.javaField}Url;
#end
#end
#end
}

View File

@ -0,0 +1,19 @@
-- 菜单 SQL
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
values(${table.menuIds[0]}, '${functionName}', '${parentMenuId}', '1', '${businessName}', '${moduleName}/${businessName}/index', 1, 0, 'C', '0', '0', '${permissionPrefix}:list', '#', 103, 1, sysdate, null, null, '${functionName}菜单');
-- 按钮 SQL
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
values(${table.menuIds[1]}, '${functionName}查询', ${table.menuIds[0]}, '1', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:query', '#', 103, 1, sysdate, null, null, '');
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
values(${table.menuIds[2]}, '${functionName}新增', ${table.menuIds[0]}, '2', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:add', '#', 103, 1, sysdate, null, null, '');
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
values(${table.menuIds[3]}, '${functionName}修改', ${table.menuIds[0]}, '3', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:edit', '#', 103, 1, sysdate, null, null, '');
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
values(${table.menuIds[4]}, '${functionName}删除', ${table.menuIds[0]}, '4', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:remove', '#', 103, 1, sysdate, null, null, '');
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
values(${table.menuIds[5]}, '${functionName}导出', ${table.menuIds[0]}, '5', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:export', '#', 103, 1, sysdate, null, null, '');

View File

@ -0,0 +1,20 @@
-- 菜单 SQL
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
values(${table.menuIds[0]}, '${functionName}', '${parentMenuId}', '1', '${businessName}', '${moduleName}/${businessName}/index', 1, 0, 'C', '0', '0', '${permissionPrefix}:list', '#', 103, 1, now(), null, null, '${functionName}菜单');
-- 按钮 SQL
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
values(${table.menuIds[1]}, '${functionName}查询', ${table.menuIds[0]}, '1', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:query', '#', 103, 1, now(), null, null, '');
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
values(${table.menuIds[2]}, '${functionName}新增', ${table.menuIds[0]}, '2', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:add', '#', 103, 1, now(), null, null, '');
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
values(${table.menuIds[3]}, '${functionName}修改', ${table.menuIds[0]}, '3', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:edit', '#', 103, 1, now(), null, null, '');
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
values(${table.menuIds[4]}, '${functionName}删除', ${table.menuIds[0]}, '4', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:remove', '#', 103, 1, now(), null, null, '');
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
values(${table.menuIds[5]}, '${functionName}导出', ${table.menuIds[0]}, '5', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:export', '#', 103, 1, now(), null, null, '');

View File

@ -0,0 +1,19 @@
-- 菜单 SQL
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
values(${table.menuIds[0]}, '${functionName}', '${parentMenuId}', '1', '${businessName}', '${moduleName}/${businessName}/index', 1, 0, 'C', '0', '0', '${permissionPrefix}:list', '#', 103, 1, sysdate(), null, null, '${functionName}菜单');
-- 按钮 SQL
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
values(${table.menuIds[1]}, '${functionName}查询', ${table.menuIds[0]}, '1', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:query', '#', 103, 1, sysdate(), null, null, '');
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
values(${table.menuIds[2]}, '${functionName}新增', ${table.menuIds[0]}, '2', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:add', '#', 103, 1, sysdate(), null, null, '');
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
values(${table.menuIds[3]}, '${functionName}修改', ${table.menuIds[0]}, '3', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:edit', '#', 103, 1, sysdate(), null, null, '');
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
values(${table.menuIds[4]}, '${functionName}删除', ${table.menuIds[0]}, '4', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:remove', '#', 103, 1, sysdate(), null, null, '');
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
values(${table.menuIds[5]}, '${functionName}导出', ${table.menuIds[0]}, '5', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:export', '#', 103, 1, sysdate(), null, null, '');

View File

@ -0,0 +1,19 @@
-- 菜单 SQL
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
values(${table.menuIds[0]}, '${functionName}', '${parentMenuId}', '1', '${businessName}', '${moduleName}/${businessName}/index', 1, 0, 'C', '0', '0', '${permissionPrefix}:list', '#', 103, 1, getdate(), null, null, '${functionName}菜单');
-- 按钮 SQL
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
values(${table.menuIds[1]}, '${functionName}查询', ${table.menuIds[0]}, '1', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:query', '#', 103, 1, getdate(), null, null, '');
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
values(${table.menuIds[2]}, '${functionName}新增', ${table.menuIds[0]}, '2', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:add', '#', 103, 1, getdate(), null, null, '');
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
values(${table.menuIds[3]}, '${functionName}修改', ${table.menuIds[0]}, '3', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:edit', '#', 103, 1, getdate(), null, null, '');
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
values(${table.menuIds[4]}, '${functionName}删除', ${table.menuIds[0]}, '4', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:remove', '#', 103, 1, getdate(), null, null, '');
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
values(${table.menuIds[5]}, '${functionName}导出', ${table.menuIds[0]}, '5', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:export', '#', 103, 1, getdate(), null, null, '');

View File

@ -0,0 +1,63 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { ${BusinessName}VO, ${BusinessName}Form, ${BusinessName}Query } from '@/api/${moduleName}/${businessName}/types';
/**
* 查询${functionName}列表
* @param query
* @returns {*}
*/
export const list${BusinessName} = (query?: ${BusinessName}Query): AxiosPromise<${BusinessName}VO[]> => {
return request({
url: '/${moduleName}/${businessName}/list',
method: 'get',
params: query
});
};
/**
* 查询${functionName}详细
* @param ${pkColumn.javaField}
*/
export const get${BusinessName} = (${pkColumn.javaField}: string | number): AxiosPromise<${BusinessName}VO> => {
return request({
url: '/${moduleName}/${businessName}/' + ${pkColumn.javaField},
method: 'get'
});
};
/**
* 新增${functionName}
* @param data
*/
export const add${BusinessName} = (data: ${BusinessName}Form) => {
return request({
url: '/${moduleName}/${businessName}',
method: 'post',
data: data
});
};
/**
* 修改${functionName}
* @param data
*/
export const update${BusinessName} = (data: ${BusinessName}Form) => {
return request({
url: '/${moduleName}/${businessName}',
method: 'put',
data: data
});
};
/**
* 删除${functionName}
* @param ${pkColumn.javaField}
*/
export const del${BusinessName} = (${pkColumn.javaField}: string | number | Array<string | number>) => {
return request({
url: '/${moduleName}/${businessName}/' + ${pkColumn.javaField},
method: 'delete'
});
};

View File

@ -0,0 +1,64 @@
export interface ${BusinessName}VO {
#foreach ($column in $columns)
#if($column.list)
/**
* $column.columnComment
*/
$column.javaField:#if($column.javaField.indexOf("id") != -1 || $column.javaField.indexOf("Id") != -1) string | number;
#elseif($column.javaType == 'Long' || $column.javaType == 'Integer' || $column.javaType == 'Double' || $column.javaType == 'Float' || $column.javaType == 'BigDecimal') number;
#elseif($column.javaType == 'Boolean') boolean;
#else string;
#end
#if($column.htmlType == "imageUpload")
/**
* ${column.columnComment}Url
*/
${column.javaField}Url: string;
#end
#end
#end
#if ($table.tree)
/**
* 子对象
*/
children: ${BusinessName}VO[];
#end
}
export interface ${BusinessName}Form extends BaseEntity {
#foreach ($column in $columns)
#if($column.insert || $column.edit)
/**
* $column.columnComment
*/
$column.javaField?:#if($column.javaField.indexOf("id") != -1 || $column.javaField.indexOf("Id") != -1) string | number;
#elseif($column.javaType == 'Long' || $column.javaType == 'Integer' || $column.javaType == 'Double' || $column.javaType == 'Float' || $column.javaType == 'BigDecimal') number;
#elseif($column.javaType == 'Boolean') boolean;
#else string;
#end
#end
#end
}
export interface ${BusinessName}Query #if(!${treeCode})extends PageQuery #end{
#foreach ($column in $columns)
#if($column.query)
/**
* $column.columnComment
*/
$column.javaField?:#if($column.javaField.indexOf("id") != -1 || $column.javaField.indexOf("Id") != -1) string | number;
#elseif($column.javaType == 'Long' || $column.javaType == 'Integer' || $column.javaType == 'Double' || $column.javaType == 'Float' || $column.javaType == 'BigDecimal') number;
#elseif($column.javaType == 'Boolean') boolean;
#else string;
#end
#end
#end
/**
* 日期范围参数
*/
params?: any;
}

View File

@ -0,0 +1,498 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
#foreach($column in $columns)
#if($column.query)
#set($dictType=$column.dictType)
#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
#set($parentheseIndex=$column.columnComment.indexOf(""))
#if($parentheseIndex != -1)
#set($comment=$column.columnComment.substring(0, $parentheseIndex))
#else
#set($comment=$column.columnComment)
#end
#if($column.htmlType == "input" || $column.htmlType == "textarea")
<el-form-item label="${comment}" prop="${column.javaField}">
<el-input v-model="queryParams.${column.javaField}" placeholder="请输入${comment}" clearable @keyup.enter="handleQuery" />
</el-form-item>
#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && "" != $dictType)
<el-form-item label="${comment}" prop="${column.javaField}">
<el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable>
<el-option v-for="dict in ${dictType}" :key="dict.value" :label="dict.label" :value="dict.value"/>
</el-select>
</el-form-item>
#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && $dictType)
<el-form-item label="${comment}" prop="${column.javaField}">
<el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable>
<el-option label="请选择字典生成" value="" />
</el-select>
</el-form-item>
#elseif($column.htmlType == "datetime" && $column.queryType != "BETWEEN")
<el-form-item label="${comment}" prop="${column.javaField}">
<el-date-picker clearable
v-model="queryParams.${column.javaField}"
type="date"
value-format="YYYY-MM-DD"
placeholder="选择${comment}"
/>
</el-form-item>
#elseif($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
<el-form-item label="${comment}" style="width: 308px">
<el-date-picker
v-model="dateRange${AttrName}"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
/>
</el-form-item>
#end
#end
#end
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="never">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd()" v-hasPermi="['${moduleName}:${businessName}:add']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="info" plain icon="Sort" @click="handleToggleExpandAll">展开/折叠</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
</template>
<el-table
ref="${businessName}TableRef"
v-loading="loading"
:data="${businessName}List"
row-key="${treeCode}"
:default-expand-all="isExpandAll"
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
>
#foreach($column in $columns)
#set($javaField=$column.javaField)
#set($parentheseIndex=$column.columnComment.indexOf(""))
#if($parentheseIndex != -1)
#set($comment=$column.columnComment.substring(0, $parentheseIndex))
#else
#set($comment=$column.columnComment)
#end
#if($column.pk)
#elseif($column.list && $column.htmlType == "datetime")
<el-table-column label="${comment}" align="center" prop="${javaField}" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.${javaField}, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
#elseif($column.list && $column.htmlType == "imageUpload")
<el-table-column label="${comment}" align="center" prop="${javaField}Url" width="100">
<template #default="scope">
<image-preview :src="scope.row.${javaField}Url" :width="50" :height="50"/>
</template>
</el-table-column>
#elseif($column.list && $column.dictType && "" != $column.dictType)
<el-table-column label="${comment}" align="center" prop="${javaField}">
<template #default="scope">
#if($column.htmlType == "checkbox")
<dict-tag :options="${column.dictType}" :value="scope.row.${javaField} ? scope.row.${javaField}.split(',') : []"/>
#else
<dict-tag :options="${column.dictType}" :value="scope.row.${javaField}"/>
#end
</template>
</el-table-column>
#elseif($column.list && "" != $javaField)
#if(${foreach.index} == 1)
<el-table-column label="${comment}" prop="${javaField}" />
#else
<el-table-column label="${comment}" align="center" prop="${javaField}" />
#end
#end
#end
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['${moduleName}:${businessName}:edit']" />
</el-tooltip>
<el-tooltip content="新增" placement="top">
<el-button link type="primary" icon="Plus" @click="handleAdd(scope.row)" v-hasPermi="['${moduleName}:${businessName}:add']" />
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['${moduleName}:${businessName}:remove']" />
</el-tooltip>
</template>
</el-table-column>
</el-table>
</el-card>
<!-- 添加或修改${functionName}对话框 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
<el-form ref="${businessName}FormRef" :model="form" :rules="rules" label-width="80px">
#foreach($column in $columns)
#set($field=$column.javaField)
#if(($column.insert || $column.edit) && !$column.pk)
#set($parentheseIndex=$column.columnComment.indexOf(""))
#if($parentheseIndex != -1)
#set($comment=$column.columnComment.substring(0, $parentheseIndex))
#else
#set($comment=$column.columnComment)
#end
#set($dictType=$column.dictType)
#if("" != $treeParentCode && $column.javaField == $treeParentCode)
<el-form-item label="${comment}" prop="${treeParentCode}">
<el-tree-select
v-model="form.${treeParentCode}"
:data="${businessName}Options"
:props="{ value: '${treeCode}', label: '${treeName}', children: 'children' }"
value-key="${treeCode}"
placeholder="请选择${comment}"
check-strictly
/>
</el-form-item>
#elseif($column.htmlType == "input")
<el-form-item label="${comment}" prop="${field}">
<el-input v-model="form.${field}" placeholder="请输入${comment}" />
</el-form-item>
#elseif($column.htmlType == "imageUpload")
<el-form-item label="${comment}" prop="${field}">
<image-upload v-model="form.${field}"/>
</el-form-item>
#elseif($column.htmlType == "fileUpload")
<el-form-item label="${comment}" prop="${field}">
<file-upload v-model="form.${field}"/>
</el-form-item>
#elseif($column.htmlType == "editor")
<el-form-item label="${comment}">
<editor v-model="form.${field}" :min-height="192"/>
</el-form-item>
#elseif($column.htmlType == "select" && "" != $dictType)
<el-form-item label="${comment}" prop="${field}">
<el-select v-model="form.${field}" placeholder="请选择${comment}">
<el-option
v-for="dict in ${dictType}"
:key="dict.value"
:label="dict.label"
#if($column.javaType == "Integer" || $column.javaType == "Long")
:value="parseInt(dict.value)"
#else
:value="dict.value"
#end
></el-option>
</el-select>
</el-form-item>
#elseif($column.htmlType == "select" && $dictType)
<el-form-item label="${comment}" prop="${field}">
<el-select v-model="form.${field}" placeholder="请选择${comment}">
<el-option label="请选择字典生成" value="" />
</el-select>
</el-form-item>
#elseif($column.htmlType == "checkbox" && "" != $dictType)
<el-form-item label="${comment}" prop="${field}">
<el-checkbox-group v-model="form.${field}">
<el-checkbox
v-for="dict in ${dictType}"
:key="dict.value"
:label="dict.value">
{{dict.label}}
</el-checkbox>
</el-checkbox-group>
</el-form-item>
#elseif($column.htmlType == "checkbox" && $dictType)
<el-form-item label="${comment}" prop="${field}">
<el-checkbox-group v-model="form.${field}">
<el-checkbox>请选择字典生成</el-checkbox>
</el-checkbox-group>
</el-form-item>
#elseif($column.htmlType == "radio" && "" != $dictType)
<el-form-item label="${comment}" prop="${field}">
<el-radio-group v-model="form.${field}">
<el-radio
v-for="dict in ${dictType}"
:key="dict.value"
#if($column.javaType == "Integer" || $column.javaType == "Long")
:value="parseInt(dict.value)"
#else
:value="dict.value"
#end
>{{dict.label}}</el-radio>
</el-radio-group>
</el-form-item>
#elseif($column.htmlType == "radio" && $dictType)
<el-form-item label="${comment}" prop="${field}">
<el-radio-group v-model="form.${field}">
<el-radio value="1">请选择字典生成</el-radio>
</el-radio-group>
</el-form-item>
#elseif($column.htmlType == "datetime")
<el-form-item label="${comment}" prop="${field}">
<el-date-picker clearable
v-model="form.${field}"
type="datetime"
value-format="YYYY-MM-DD HH:mm:ss"
placeholder="选择${comment}"
/>
</el-form-item>
#elseif($column.htmlType == "textarea")
<el-form-item label="${comment}" prop="${field}">
<el-input v-model="form.${field}" type="textarea" placeholder="请输入内容" />
</el-form-item>
#end
#end
#end
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="${BusinessName}" lang="ts">
import { list${BusinessName}, get${BusinessName}, del${BusinessName}, add${BusinessName}, update${BusinessName} } from "@/api/${moduleName}/${businessName}";
import { ${BusinessName}VO, ${BusinessName}Query, ${BusinessName}Form } from '@/api/${moduleName}/${businessName}/types';
type ${BusinessName}Option = {
${treeCode}: number;
${treeName}: string;
children?: ${BusinessName}Option[];
}
const { proxy } = getCurrentInstance() as ComponentInternalInstance;;
#if(${dicts} != '')
#set($dictsNoSymbol=$dicts.replace("'", ""))
const { ${dictsNoSymbol} } = toRefs<any>(proxy?.useDict(${dicts}));
#end
const ${businessName}List = ref<${BusinessName}VO[]>([]);
const ${businessName}Options = ref<${BusinessName}Option[]>([]);
const buttonLoading = ref(false);
const showSearch = ref(true);
const isExpandAll = ref(true);
const loading = ref(false);
const queryFormRef = ref<ElFormInstance>();
const ${businessName}FormRef = ref<ElFormInstance>();
const ${businessName}TableRef = ref<ElTableInstance>()
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
#foreach ($column in $columns)
#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
const dateRange${AttrName} = ref<[DateModelType, DateModelType]>(['', '']);
#end
#end
const initFormData: ${BusinessName}Form = {
#foreach ($column in $columns)
#if($column.insert || $column.edit)
#if($column.htmlType == "checkbox")
$column.javaField: []#if($foreach.count != $columns.size()),#end
#else
$column.javaField: undefined#if($foreach.count != $columns.size()),#end
#end
#end
#end
}
const data = reactive<PageData<${BusinessName}Form, ${BusinessName}Query>>({
form: {...initFormData},
queryParams: {
#foreach ($column in $columns)
#if($column.query)
#if($column.htmlType != "datetime" || $column.queryType != "BETWEEN")
$column.javaField: undefined,
#end
#end
#end
params: {
#foreach ($column in $columns)
#if($column.query)
#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
$column.javaField: undefined#if($foreach.count != $columns.size()),#end
#end
#end
#end
}
},
rules: {
#foreach ($column in $columns)
#if($column.insert || $column.edit)
#if($column.required)
#set($parentheseIndex=$column.columnComment.indexOf(""))
#if($parentheseIndex != -1)
#set($comment=$column.columnComment.substring(0, $parentheseIndex))
#else
#set($comment=$column.columnComment)
#end
$column.javaField: [
{ required: true, message: "$comment不能为空", trigger: #if($column.htmlType == "select" || $column.htmlType == "radio")"change"#else"blur"#end }
]#if($foreach.count != $columns.size()),#end
#end
#end
#end
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询${functionName}列表 */
const getList = async () => {
loading.value = true;
#foreach ($column in $columns)
#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
queryParams.value.params = {};
#break
#end
#end
#foreach ($column in $columns)
#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
proxy?.addDateRange(queryParams.value, dateRange${AttrName}.value, '${AttrName}');
#end
#end
const res = await list${BusinessName}(queryParams.value);
const data = proxy?.handleTree<${BusinessName}VO>(res.data, "${treeCode}", "${treeParentCode}");
if (data) {
${businessName}List.value = data;
loading.value = false;
}
}
/** 查询${functionName}下拉树结构 */
const getTreeselect = async () => {
const res = await list${BusinessName}();
${businessName}Options.value = [];
const data: ${BusinessName}Option = { ${treeCode}: 0, ${treeName}: '顶级节点', children: [] };
data.children = proxy?.handleTree<${BusinessName}Option>(res.data, "${treeCode}", "${treeParentCode}");
${businessName}Options.value.push(data);
}
// 取消按钮
const cancel = () => {
reset();
dialog.visible = false;
}
// 表单重置
const reset = () => {
form.value = {...initFormData}
${businessName}FormRef.value?.resetFields();
}
/** 搜索按钮操作 */
const handleQuery = () => {
getList();
}
/** 重置按钮操作 */
const resetQuery = () => {
#foreach ($column in $columns)
#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
dateRange${AttrName}.value = ['', ''];
#end
#end
queryFormRef.value?.resetFields();
handleQuery();
}
/** 新增按钮操作 */
const handleAdd = (row?: ${BusinessName}VO) => {
reset();
getTreeselect();
if (row != null && row.${treeCode}) {
form.value.${treeParentCode} = row.${treeCode};
} else {
form.value.${treeParentCode} = 0;
}
dialog.visible = true;
dialog.title = "添加${functionName}";
}
/** 展开/折叠操作 */
const handleToggleExpandAll = () => {
isExpandAll.value = !isExpandAll.value;
toggleExpandAll(${businessName}List.value, isExpandAll.value)
}
/** 展开/折叠操作 */
const toggleExpandAll = (data: ${BusinessName}VO[], status: boolean) => {
data.forEach((item) => {
${businessName}TableRef.value?.toggleRowExpansion(item, status)
if (item.children && item.children.length > 0) toggleExpandAll(item.children, status)
})
}
/** 修改按钮操作 */
const handleUpdate = async (row: ${BusinessName}VO) => {
reset();
await getTreeselect();
if (row != null) {
form.value.${treeParentCode} = row.${treeParentCode};
}
const res = await get${BusinessName}(row.${pkColumn.javaField});
Object.assign(form.value, res.data);
#foreach ($column in $columns)
#if($column.htmlType == "checkbox")
form.value.$column.javaField = form.value.${column.javaField}.split(",");
#end
#end
dialog.visible = true;
dialog.title = "修改${functionName}";
}
/** 提交按钮 */
const submitForm = () => {
${businessName}FormRef.value?.validate(async (valid: boolean) => {
if (valid) {
buttonLoading.value = true;
#foreach ($column in $columns)
#if($column.htmlType == "checkbox")
form.value.$column.javaField = form.value.${column.javaField}.join(",");
#end
#end
if (form.value.${pkColumn.javaField}) {
await update${BusinessName}(form.value).finally(() => buttonLoading.value = false);
} else {
await add${BusinessName}(form.value).finally(() => buttonLoading.value = false);
}
proxy?.#[[$modal]]#.msgSuccess("操作成功");
dialog.visible = false;
getList();
}
});
}
/** 删除按钮操作 */
const handleDelete = async (row: ${BusinessName}VO) => {
await proxy?.#[[$modal]]#.confirm('是否确认删除${functionName}编号为"' + row.${pkColumn.javaField} + '"的数据项?');
loading.value = true;
await del${BusinessName}(row.${pkColumn.javaField}).finally(() => loading.value = false);
await getList();
proxy?.#[[$modal]]#.msgSuccess("删除成功");
}
onMounted(() => {
getList();
});
</script>

View File

@ -0,0 +1,459 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
#foreach($column in $columns)
#if($column.query)
#set($dictType=$column.dictType)
#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
#set($parentheseIndex=$column.columnComment.indexOf(""))
#if($parentheseIndex != -1)
#set($comment=$column.columnComment.substring(0, $parentheseIndex))
#else
#set($comment=$column.columnComment)
#end
#if($column.htmlType == "input" || $column.htmlType == "textarea")
<el-form-item label="${comment}" prop="${column.javaField}">
<el-input v-model="queryParams.${column.javaField}" placeholder="请输入${comment}" clearable @keyup.enter="handleQuery" />
</el-form-item>
#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && "" != $dictType)
<el-form-item label="${comment}" prop="${column.javaField}">
<el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable >
<el-option v-for="dict in ${dictType}" :key="dict.value" :label="dict.label" :value="dict.value"/>
</el-select>
</el-form-item>
#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && $dictType)
<el-form-item label="${comment}" prop="${column.javaField}">
<el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable >
<el-option label="请选择字典生成" value="" />
</el-select>
</el-form-item>
#elseif($column.htmlType == "datetime" && $column.queryType != "BETWEEN")
<el-form-item label="${comment}" prop="${column.javaField}">
<el-date-picker clearable
v-model="queryParams.${column.javaField}"
type="date"
value-format="YYYY-MM-DD"
placeholder="请选择${comment}"
/>
</el-form-item>
#elseif($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
<el-form-item label="${comment}" style="width: 308px">
<el-date-picker
v-model="dateRange${AttrName}"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
/>
</el-form-item>
#end
#end
#end
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="never">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['${moduleName}:${businessName}:add']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['${moduleName}:${businessName}:edit']">修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['${moduleName}:${businessName}:remove']">删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['${moduleName}:${businessName}:export']">导出</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" :data="${businessName}List" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
#foreach($column in $columns)
#set($javaField=$column.javaField)
#set($parentheseIndex=$column.columnComment.indexOf(""))
#if($parentheseIndex != -1)
#set($comment=$column.columnComment.substring(0, $parentheseIndex))
#else
#set($comment=$column.columnComment)
#end
#if($column.pk)
<el-table-column label="${comment}" align="center" prop="${javaField}" v-if="${column.list}" />
#elseif($column.list && $column.htmlType == "datetime")
<el-table-column label="${comment}" align="center" prop="${javaField}" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.${javaField}, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
#elseif($column.list && $column.htmlType == "imageUpload")
<el-table-column label="${comment}" align="center" prop="${javaField}Url" width="100">
<template #default="scope">
<image-preview :src="scope.row.${javaField}Url" :width="50" :height="50"/>
</template>
</el-table-column>
#elseif($column.list && $column.dictType && "" != $column.dictType)
<el-table-column label="${comment}" align="center" prop="${javaField}">
<template #default="scope">
#if($column.htmlType == "checkbox")
<dict-tag :options="${column.dictType}" :value="scope.row.${javaField} ? scope.row.${javaField}.split(',') : []"/>
#else
<dict-tag :options="${column.dictType}" :value="scope.row.${javaField}"/>
#end
</template>
</el-table-column>
#elseif($column.list && "" != $javaField)
<el-table-column label="${comment}" align="center" prop="${javaField}" />
#end
#end
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['${moduleName}:${businessName}:edit']"></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['${moduleName}:${businessName}:remove']"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
<!-- 添加或修改${functionName}对话框 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
<el-form ref="${businessName}FormRef" :model="form" :rules="rules" label-width="80px">
#foreach($column in $columns)
#set($field=$column.javaField)
#if(($column.insert || $column.edit) && !$column.pk)
#set($parentheseIndex=$column.columnComment.indexOf(""))
#if($parentheseIndex != -1)
#set($comment=$column.columnComment.substring(0, $parentheseIndex))
#else
#set($comment=$column.columnComment)
#end
#set($dictType=$column.dictType)
#if($column.htmlType == "input")
<el-form-item label="${comment}" prop="${field}">
<el-input v-model="form.${field}" placeholder="请输入${comment}" />
</el-form-item>
#elseif($column.htmlType == "imageUpload")
<el-form-item label="${comment}" prop="${field}">
<image-upload v-model="form.${field}"/>
</el-form-item>
#elseif($column.htmlType == "fileUpload")
<el-form-item label="${comment}" prop="${field}">
<file-upload v-model="form.${field}"/>
</el-form-item>
#elseif($column.htmlType == "editor")
<el-form-item label="${comment}">
<editor v-model="form.${field}" :min-height="192"/>
</el-form-item>
#elseif($column.htmlType == "select" && "" != $dictType)
<el-form-item label="${comment}" prop="${field}">
<el-select v-model="form.${field}" placeholder="请选择${comment}">
<el-option
v-for="dict in ${dictType}"
:key="dict.value"
:label="dict.label"
#if($column.javaType == "Integer" || $column.javaType == "Long")
:value="parseInt(dict.value)"
#else
:value="dict.value"
#end
></el-option>
</el-select>
</el-form-item>
#elseif($column.htmlType == "select" && $dictType)
<el-form-item label="${comment}" prop="${field}">
<el-select v-model="form.${field}" placeholder="请选择${comment}">
<el-option label="请选择字典生成" value="" />
</el-select>
</el-form-item>
#elseif($column.htmlType == "checkbox" && "" != $dictType)
<el-form-item label="${comment}" prop="${field}">
<el-checkbox-group v-model="form.${field}">
<el-checkbox
v-for="dict in ${dictType}"
:key="dict.value"
:label="dict.value">
{{dict.label}}
</el-checkbox>
</el-checkbox-group>
</el-form-item>
#elseif($column.htmlType == "checkbox" && $dictType)
<el-form-item label="${comment}" prop="${field}">
<el-checkbox-group v-model="form.${field}">
<el-checkbox>请选择字典生成</el-checkbox>
</el-checkbox-group>
</el-form-item>
#elseif($column.htmlType == "radio" && "" != $dictType)
<el-form-item label="${comment}" prop="${field}">
<el-radio-group v-model="form.${field}">
<el-radio
v-for="dict in ${dictType}"
:key="dict.value"
#if($column.javaType == "Integer" || $column.javaType == "Long")
:value="parseInt(dict.value)"
#else
:value="dict.value"
#end
>{{dict.label}}</el-radio>
</el-radio-group>
</el-form-item>
#elseif($column.htmlType == "radio" && $dictType)
<el-form-item label="${comment}" prop="${field}">
<el-radio-group v-model="form.${field}">
<el-radio value="1">请选择字典生成</el-radio>
</el-radio-group>
</el-form-item>
#elseif($column.htmlType == "datetime")
<el-form-item label="${comment}" prop="${field}">
<el-date-picker clearable
v-model="form.${field}"
type="datetime"
value-format="YYYY-MM-DD HH:mm:ss"
placeholder="请选择${comment}">
</el-date-picker>
</el-form-item>
#elseif($column.htmlType == "textarea")
<el-form-item label="${comment}" prop="${field}">
<el-input v-model="form.${field}" type="textarea" placeholder="请输入内容" />
</el-form-item>
#end
#end
#end
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="${BusinessName}" lang="ts">
import { list${BusinessName}, get${BusinessName}, del${BusinessName}, add${BusinessName}, update${BusinessName} } from '@/api/${moduleName}/${businessName}';
import { ${BusinessName}VO, ${BusinessName}Query, ${BusinessName}Form } from '@/api/${moduleName}/${businessName}/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
#if(${dicts} != '')
#set($dictsNoSymbol=$dicts.replace("'", ""))
const { ${dictsNoSymbol} } = toRefs<any>(proxy?.useDict(${dicts}));
#end
const ${businessName}List = ref<${BusinessName}VO[]>([]);
const buttonLoading = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
#foreach ($column in $columns)
#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
const dateRange${AttrName} = ref<[DateModelType, DateModelType]>(['', '']);
#end
#end
const queryFormRef = ref<ElFormInstance>();
const ${businessName}FormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const initFormData: ${BusinessName}Form = {
#foreach ($column in $columns)
#if($column.insert || $column.edit)
#if($column.htmlType == "checkbox")
$column.javaField: []#if($foreach.count != $columns.size()),#end
#else
$column.javaField: undefined#if($foreach.count != $columns.size()),#end
#end
#end
#end
}
const data = reactive<PageData<${BusinessName}Form, ${BusinessName}Query>>({
form: {...initFormData},
queryParams: {
pageNum: 1,
pageSize: 10,
#foreach ($column in $columns)
#if($column.query)
#if($column.htmlType != "datetime" || $column.queryType != "BETWEEN")
$column.javaField: undefined,
#end
#end
#end
params: {
#foreach ($column in $columns)
#if($column.query)
#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
$column.javaField: undefined#if($foreach.count != $columns.size()),#end
#end
#end
#end
}
},
rules: {
#foreach ($column in $columns)
#if($column.insert || $column.edit)
#if($column.required)
#set($parentheseIndex=$column.columnComment.indexOf(""))
#if($parentheseIndex != -1)
#set($comment=$column.columnComment.substring(0, $parentheseIndex))
#else
#set($comment=$column.columnComment)
#end
$column.javaField: [
{ required: true, message: "$comment不能为空", trigger: #if($column.htmlType == "select" || $column.htmlType == "radio")"change"#else"blur"#end }
]#if($foreach.count != $columns.size()),#end
#end
#end
#end
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询${functionName}列表 */
const getList = async () => {
loading.value = true;
#foreach ($column in $columns)
#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
queryParams.value.params = {};
#break
#end
#end
#foreach ($column in $columns)
#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
proxy?.addDateRange(queryParams.value, dateRange${AttrName}.value, '${AttrName}');
#end
#end
const res = await list${BusinessName}(queryParams.value);
${businessName}List.value = res.rows;
total.value = res.total;
loading.value = false;
}
/** 取消按钮 */
const cancel = () => {
reset();
dialog.visible = false;
}
/** 表单重置 */
const reset = () => {
form.value = {...initFormData};
${businessName}FormRef.value?.resetFields();
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
}
/** 重置按钮操作 */
const resetQuery = () => {
#foreach ($column in $columns)
#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
dateRange${AttrName}.value = ['', ''];
#end
#end
queryFormRef.value?.resetFields();
handleQuery();
}
/** 多选框选中数据 */
const handleSelectionChange = (selection: ${BusinessName}VO[]) => {
ids.value = selection.map(item => item.${pkColumn.javaField});
single.value = selection.length != 1;
multiple.value = !selection.length;
}
/** 新增按钮操作 */
const handleAdd = () => {
reset();
dialog.visible = true;
dialog.title = "添加${functionName}";
}
/** 修改按钮操作 */
const handleUpdate = async (row?: ${BusinessName}VO) => {
reset();
const _${pkColumn.javaField} = row?.${pkColumn.javaField} || ids.value[0]
const res = await get${BusinessName}(_${pkColumn.javaField});
Object.assign(form.value, res.data);
#foreach ($column in $columns)
#if($column.htmlType == "checkbox")
form.value.$column.javaField = form.value.${column.javaField}.split(",");
#end
#end
dialog.visible = true;
dialog.title = "修改${functionName}";
}
/** 提交按钮 */
const submitForm = () => {
${businessName}FormRef.value?.validate(async (valid: boolean) => {
if (valid) {
buttonLoading.value = true;
#foreach ($column in $columns)
#if($column.htmlType == "checkbox")
form.value.$column.javaField = form.value.${column.javaField}.join(",");
#end
#end
if (form.value.${pkColumn.javaField}) {
await update${BusinessName}(form.value).finally(() => buttonLoading.value = false);
} else {
await add${BusinessName}(form.value).finally(() => buttonLoading.value = false);
}
proxy?.#[[$modal]]#.msgSuccess("操作成功");
dialog.visible = false;
await getList();
}
});
}
/** 删除按钮操作 */
const handleDelete = async (row?: ${BusinessName}VO) => {
const _${pkColumn.javaField}s = row?.${pkColumn.javaField} || ids.value;
await proxy?.#[[$modal]]#.confirm('是否确认删除${functionName}编号为"' + _${pkColumn.javaField}s + '"的数据项?').finally(() => loading.value = false);
await del${BusinessName}(_${pkColumn.javaField}s);
proxy?.#[[$modal]]#.msgSuccess("删除成功");
await getList();
}
/** 导出按钮操作 */
const handleExport = () => {
proxy?.download('${moduleName}/${businessName}/export', {
...queryParams.value
}, `${businessName}_#[[${new Date().getTime()}]]#.xlsx`)
}
onMounted(() => {
getList();
});
</script>

View File

@ -0,0 +1,7 @@
<?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="${packageName}.mapper.${ClassName}Mapper">
</mapper>

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-modules</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>
<artifactId>ruoyi-job</artifactId>
<description>
任务调度
</description>
<dependencies>
<!-- 通用工具-->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-json</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-job</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1 @@
package org.dromara.job;

View File

@ -0,0 +1,23 @@
package org.dromara.job.snailjob;
import com.aizuda.snailjob.client.job.core.annotation.JobExecutor;
import com.aizuda.snailjob.client.job.core.dto.JobArgs;
import com.aizuda.snailjob.client.model.ExecuteResult;
import com.aizuda.snailjob.common.core.util.JsonUtil;
import com.aizuda.snailjob.common.log.SnailJobLog;
import org.springframework.stereotype.Component;
/**
* @author opensnail
* @date 2024-05-17
*/
@Component
@JobExecutor(name = "testJobExecutor")
public class TestAnnoJobExecutor {
public ExecuteResult jobExecute(JobArgs jobArgs) {
SnailJobLog.LOCAL.info("testJobExecutor. jobArgs:{}", JsonUtil.toJsonString(jobArgs));
SnailJobLog.REMOTE.info("testJobExecutor. jobArgs:{}", JsonUtil.toJsonString(jobArgs));
return ExecuteResult.success("测试成功");
}
}

View File

@ -0,0 +1,19 @@
package org.dromara.job.snailjob;
import com.aizuda.snailjob.client.job.core.dto.JobArgs;
import com.aizuda.snailjob.client.job.core.executor.AbstractJobExecutor;
import com.aizuda.snailjob.client.model.ExecuteResult;
import org.springframework.stereotype.Component;
/**
* @author opensnail
* @date 2024-05-17
*/
@Component
public class TestClassJobExecutor extends AbstractJobExecutor {
@Override
protected ExecuteResult doJobExecute(JobArgs jobArgs) {
return ExecuteResult.success("TestJobExecutor测试成功");
}
}

View File

@ -0,0 +1,216 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-modules</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ruoyi-system</artifactId>
<description>
system系统模块
</description>
<dependencies>
<!-- JSON解析FastJSON -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.32</version>
</dependency>
<!-- word转pdf转图片 -->
<!-- docx4j 核心 -->
<dependency>
<groupId>org.docx4j</groupId>
<artifactId>docx4j-core</artifactId>
<version>11.5.3</version>
</dependency>
<!-- JAXB 引擎 -->
<dependency>
<groupId>org.docx4j</groupId>
<artifactId>docx4j-JAXB-ReferenceImpl</artifactId>
<version>11.5.3</version>
</dependency>
<!-- XSL-FO 输出 PDF -->
<dependency>
<groupId>org.docx4j</groupId>
<artifactId>docx4j-export-fo</artifactId>
<version>11.5.3</version>
<exclusions>
<exclusion>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.pdfbox</groupId>
<artifactId>fontbox</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- PDF 渲染用的 PDFBox -->
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>2.0.29</version>
</dependency>
<!-- 必须配套引入 fontbox否则运行时报错 -->
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>fontbox</artifactId>
<version>2.0.29</version>
</dependency>
<!-- word 模版导出 poi-tl 模板引擎 -->
<dependency>
<groupId>com.deepoove</groupId>
<artifactId>poi-tl</artifactId>
<version>1.12.2</version> <!-- 建议使用最新版本 -->
</dependency>
<!-- 在pdf上生成二维码 -->
<!-- 支持中文字体 -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-asian</artifactId>
<version>5.2.0</version>
</dependency>
<!-- iText -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.13.3</version>
</dependency>
<!-- ZXing -->
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
<version>3.5.2</version>
</dependency>
<!-- 通用工具-->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-core</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-doc</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-mybatis</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-translation</artifactId>
</dependency>
<!-- OSS功能模块 -->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-oss</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-log</artifactId>
</dependency>
<!-- excel-->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-excel</artifactId>
</dependency>
<!-- SMS功能模块 -->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-sms</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-tenant</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-security</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-web</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-idempotent</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-sensitive</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-encrypt</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-sse</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-jts</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-workflow</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>2.13.9</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna-platform</artifactId>
<version>5.15.0</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,28 @@
package org.dromara.common.constant;
import java.io.File;
/**
* @author lilemy
* @date 2025/4/23 11:09
*/
public interface DesignMapFileConstant {
String DXF_BASE_PATH = "file" + File.separator + "resource" + File.separator + "design";
/**
* WGS 84
*/
String EPSG4326 = "4326";
String EPSG4524 = "4524";
String DXFFileSuffix = "dxf";
String JSONFileSuffix = "json";
static String getDxfProjectPath(Long projectId) {
return DXF_BASE_PATH + File.separator + projectId;
}
}

View File

@ -0,0 +1,15 @@
package org.dromara.common.constant;
/**
* @author lilemy
* @date 2025/4/24 17:44
*/
public interface GeoJsonConstant {
String POINT = "Point";
String LINE = "LineString";
String POLYGON = "Polygon";
}

View File

@ -0,0 +1,14 @@
package org.dromara.common.constant;
/**
* @Author: Cory·铁憨憨
* @Date: 2025-07-03-14:12
* @Description: minio存储路线
*/
public interface MinioPathConstant {
// 联系单
String ContactNotice = "contactNotice";
// 联系单模板
String ContactNoticeTemplate = "contactNotice/template";
}

View File

@ -0,0 +1,9 @@
package org.dromara.common.constant;
/**
* @Author 铁憨憨
* @Date 2025/7/18 10:38
* @Version 1.0
*/public interface businessConstant {
String REDIS_BAIDU_KEY = "baidu:access_token"; //百度token存储在redis中的key
}

View File

@ -0,0 +1,24 @@
package org.dromara.common.enums;
import lombok.Getter;
/**
* @author lilemy
* @date 2025/4/14 14:03
*/
@Getter
public enum DocumentStatusEnum {
NORMAL("正常", "0"),
DELETE("删除", "1");
private final String text;
private final String value;
DocumentStatusEnum(String text, String value) {
this.text = text;
this.value = value;
}
}

View File

@ -0,0 +1,25 @@
package org.dromara.common.enums;
import lombok.Getter;
/**
* @author lilemy
* @date 2025/4/14 10:42
*/
@Getter
public enum DocumentTypeEnum {
FOLDER("文件夹", "1"),
FILE("文件", "2"),
PICTURE("图片", "3");
private final String text;
private final String value;
DocumentTypeEnum(String text, String value) {
this.text = text;
this.value = value;
}
}

View File

@ -0,0 +1,26 @@
package org.dromara.common.utils;
import java.math.BigDecimal;
import java.math.RoundingMode;
/**
* @author lilemy
* @date 2025/5/27 9:16
*/
public class BigDecimalUtil {
/**
* 计算百分比
*
* @param dividend 被除数
* @param divisor 除数
* @return 百分比保留2位小数如 12.34%
*/
public static BigDecimal toPercentage(BigDecimal dividend, BigDecimal divisor) {
if (dividend == null || divisor == null || divisor.compareTo(BigDecimal.ZERO) == 0) {
return new BigDecimal(0);
}
return dividend.divide(divisor, 2, RoundingMode.HALF_UP).multiply(new BigDecimal("100"));
}
}

View File

@ -0,0 +1,100 @@
package org.dromara.common.utils;
import org.apache.poi.util.Units;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
* @author lilemy
* @date 2025/4/17 14:53
*/
public class DocumentUtil {
/**
* 在给定的 run 里插入图片,并按原始大小(或缩放后大小)设置宽高。
*
* @param run 要插入图片的 XWPFRun
* @param imagePath 本地图片路径或 URL这里示例用本地文件
* @param document 当前文档对象
* @param maxWidthPx 最大允许宽度(像素),如果原图更宽就按比例缩放;设置为 <=0 则不缩放
*/
public static void insertImageDynamic(XWPFRun run,
String imagePath,
XWPFDocument document,
int maxWidthPx) throws Exception {
// 1. 先把图片读到 byte[]
byte[] imgBytes = Files.readAllBytes(Paths.get(imagePath));
// 2. 用 ImageIO 读出宽高(像素)
BufferedImage img = ImageIO.read(new ByteArrayInputStream(imgBytes));
int widthPx = img.getWidth();
int heightPx = img.getHeight();
// 3. 如果指定了最大宽度,而且原图更宽,则按比例缩放
if (maxWidthPx > 0 && widthPx > maxWidthPx) {
double ratio = (double) maxWidthPx / widthPx;
widthPx = maxWidthPx;
heightPx = (int) (heightPx * ratio);
}
// 4. 把像素转换成 EMU
int widthEmu = Units.pixelToEMU(widthPx);
int heightEmu = Units.pixelToEMU(heightPx);
// 5. 插入图片
String lower = imagePath.toLowerCase();
int format = lower.endsWith(".png") ? XWPFDocument.PICTURE_TYPE_PNG
: lower.endsWith(".gif") ? XWPFDocument.PICTURE_TYPE_GIF
: lower.endsWith(".jpeg") ? XWPFDocument.PICTURE_TYPE_JPEG
: lower.endsWith(".jpg") ? XWPFDocument.PICTURE_TYPE_JPEG
: XWPFDocument.PICTURE_TYPE_PNG; // 默认当 PNG
try (InputStream picIn = new ByteArrayInputStream(imgBytes)) {
run.addPicture(picIn, format, imagePath, widthEmu, heightEmu);
}
}
/**
* 递归将 sourceDir 中的所有文件和子目录,按照相对于 rootDir 的路径写入 ZipOutputStream。
*
* @param rootDir 源目录的根,用来计算相对路径
* @param sourceDir 当前递归到的目录
* @param zos ZIP 输出流
*/
public static void zipDirectory(Path rootDir, Path sourceDir, ZipOutputStream zos) throws IOException {
// 遍历当前目录下的所有文件和文件夹
try (DirectoryStream<Path> stream = Files.newDirectoryStream(sourceDir)) {
for (Path entry : stream) {
if (Files.isDirectory(entry)) {
// 如果是目录,递归
zipDirectory(rootDir, entry, zos);
} else {
// 如果是文件,创建一个 ZipEntry路径以 '/' 分隔
Path relativePath = rootDir.relativize(entry);
String zipEntryName = relativePath.toString().replace(File.separatorChar, '/');
ZipEntry zipEntry = new ZipEntry(zipEntryName);
zos.putNextEntry(zipEntry);
// 把文件内容写入 ZIP
try (InputStream is = Files.newInputStream(entry)) {
byte[] buffer = new byte[4096];
int len;
while ((len = is.read(buffer)) != -1) {
zos.write(buffer, 0, len);
}
}
zos.closeEntry();
}
}
}
}
}

View File

@ -0,0 +1,97 @@
package org.dromara.common.utils;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.exception.ServiceException;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.List;
/**
* @author lilemy
* @date 2025/4/23 10:42
*/
@Slf4j
public class Dxf2JsonUtil {
/**
* dxf转json
*
* @param exePath dxf2json.exe路径
* @param inputDXFPath 输入dxf文件路径
* @param outputJSONPath 输出json文件路径
* @param sourceEPSG 源坐标系
* @param targetEPSG 目标坐标系
*/
public static void dxf2json(String exePath, String inputDXFPath, String outputJSONPath, String sourceEPSG, String targetEPSG) {
// 判断对应文件是否存在
File exeFile = new File(exePath);
if (!exeFile.exists()) {
throw new ServiceException("转换程序不存在!");
}
File inputDXFFile = new File(inputDXFPath);
if (!inputDXFFile.exists()) {
throw new ServiceException("待转换 dxf 文件不存在!");
}
// 构造命令行参数
List<String> parameters = buildParameter(exePath, inputDXFPath, outputJSONPath, sourceEPSG, targetEPSG);
// 执行命令行
ProcessBuilder builder = new ProcessBuilder(parameters);
// 合并标准错误和输出
builder.redirectErrorStream(true);
try {
Process process = builder.start();
// 读取输出
BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream(), "GBK")
);
String line;
log.info("dxf 转 json 程序开始执行,程序路径:{},输入 dxf 路径:{},输出 json 文件路径:{},源坐标系:{},模板坐标系:{}",
exePath, inputDXFPath, outputJSONPath, sourceEPSG, targetEPSG);
while ((line = reader.readLine()) != null) {
log.info("dxf 转 json 程序执行中:{}", line);
JSONObject jsonObject = JSONUtil.parseObj(line);
Integer code = jsonObject.get("code", Integer.class);
if (code != 0 && code != 200) {
throw new ServiceException("dxf 转 json 程序执行出错!");
}
}
int exitCode = process.waitFor();
log.info("dxf 转 json 程序执行完毕,程序退出码:{}", exitCode);
reader.close();
} catch (IOException | InterruptedException e) {
log.error("执行 dxf 转 json 命令行时出错", e);
}
}
/**
* 构造命令行参数
*
* @param exePath dxf2json.exe路径
* @param inputDXFPath 输入dxf文件路径
* @param outputJSONPath 输出json文件路径
* @param sourceEPSG 源坐标系
* @param targetEPSG 目标坐标系
* @return 命令行参数
*/
public static List<String> buildParameter(String exePath,
String inputDXFPath,
String outputJSONPath,
String sourceEPSG,
String targetEPSG) {
// 构造命令行
return Arrays.asList(
exePath,
inputDXFPath,
outputJSONPath,
sourceEPSG,
targetEPSG
);
}
}

View File

@ -0,0 +1,24 @@
package org.dromara.common.utils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.net.http.HttpClient;
import java.time.Duration;
/**
* @Author 铁憨憨
* @Date 2025/7/18 10:16
* @Version 1.0
*
* HttpClient 设计为可重用、线程安全的组件,其内部维护了连接池等资源,适合在多个接口调用中共享使用,所以交给Spring Bean 管理
*/
@Configuration
public class HttpClientConfig {
@Bean
public HttpClient httpClient() {
return HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.build();
}
}

View File

@ -0,0 +1,51 @@
package org.dromara.common.utils;
import cn.hutool.core.util.HexUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.symmetric.AES;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* @author lilemy
* @date 2025/6/25 10:57
*/
@Slf4j
@Component
public class IdCardEncryptorUtil {
@Value("${id-card.encrypt-key}")
private String encryptKeyHex;
private AES aes;
@PostConstruct
public void init() {
byte[] keyBytes = HexUtil.decodeHex(encryptKeyHex);
this.aes = SecureUtil.aes(keyBytes);
log.info("身份证 AES 加解密工具初始化成功");
}
/**
* 加密
*
* @param idCard 身份证号码
* @return 加密后的身份证号码
*/
public String encrypt(String idCard) {
return aes.encryptBase64(idCard);
}
/**
* 解密
*
* @param encrypted 密文
* @return 解密后的身份证号码
*/
public String decrypt(String encrypted) {
return aes.decryptStr(encrypted);
}
}

View File

@ -0,0 +1,270 @@
package org.dromara.common.utils;
import org.dromara.common.core.constant.HttpStatus;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.constant.GeoJsonConstant;
import org.dromara.facility.domain.dto.geojson.FacFeatureByPlane;
import org.dromara.facility.domain.dto.geojson.FacFeatureByPoint;
import org.dromara.facility.domain.dto.geojson.FacGeometry;
import org.dromara.facility.domain.dto.geojson.FacGeometryByPoint;
import org.locationtech.jts.geom.*;
import org.locationtech.jts.index.strtree.STRtree;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @author lilemy
* @date 2025/4/24 11:48
*/
public class JSTUtil {
private static final GeometryFactory geometryFactory = new GeometryFactory();
/**
* 获取最近点的名称
*
* @param target 目标点
* @param nameGeoJson 点对象列表
* @return 最近点的名称
*/
public static String findNearestText(List<Double> target, List<FacFeatureByPoint> nameGeoJson) {
Point targetPoint = geometryFactory.createPoint(new Coordinate(target.get(0), target.get(1)));
FacFeatureByPoint nearestFeature = null;
double minDistance = Double.MAX_VALUE;
for (FacFeatureByPoint feature : nameGeoJson) {
FacGeometryByPoint geometry = feature.getGeometry();
List<Double> coords = geometry.getCoordinates();
if (coords != null && coords.size() == 2) {
Point currentPoint = geometryFactory.createPoint(new Coordinate(coords.get(0), coords.get(1)));
double distance = targetPoint.distance(currentPoint);
if (distance < minDistance) {
minDistance = distance;
nearestFeature = feature;
}
}
}
if (nearestFeature != null && nearestFeature.getProperties() != null) {
return nearestFeature.getProperties().getText();
}
return null; // 如果没找到合适的点
}
/**
* 点是否在平面内
*
* @param planeLists 平面坐标
* @param pointList 点坐标
* @return 点是否在平面内
*/
public static Boolean pointIsWithInPlane(List<List<Double>> planeLists, List<Double> pointList) {
// 构建平面
Coordinate[] coordinates = getPlaneCoordinate(planeLists);
Polygon polygon = geometryFactory.createPolygon(coordinates);
// 构建待判断点
Point point = geometryFactory.createPoint(new Coordinate(pointList.get(0), pointList.get(1)));
// 判断是否在多边形内
return polygon.contains(point);
}
/**
* 获取平面内点列表集合
*
* @param planeLists 平面坐标列表
* @param pointLists 点坐标列表集合
* @return 平面内点坐标列表集合
*/
public static List<List<Double>> getPointInPlaneList(List<List<Double>> planeLists, List<List<Double>> pointLists) {
// 构建平面
Coordinate[] coordinates = getPlaneCoordinate(planeLists);
LinearRing shell = geometryFactory.createLinearRing(coordinates);
Polygon polygon = geometryFactory.createPolygon(shell);
// 获取平面内点结合
return pointLists.stream().filter(pointList -> {
// 构建待判断点
Point point = geometryFactory.createPoint(new Coordinate(pointList.get(0), pointList.get(1)));
// 判断是否在多边形内
return polygon.contains(point);
}).toList();
}
/**
* 平面是否在平面内
*
* @param referencePlane 参考平面
* @param comparePlane 比较平面
* @return 平面是否在平面内
*/
public static Boolean planeIsWithInPlane(List<List<Double>> referencePlane, List<List<Double>> comparePlane) {
// 构建参考平面
Coordinate[] referenceCoordinates = getPlaneCoordinate(referencePlane);
Polygon referencePolygon = geometryFactory.createPolygon(referenceCoordinates);
// 构建比较平面
Coordinate[] compareCoordinates = getPlaneCoordinate(comparePlane);
Polygon comparePolygon = geometryFactory.createPolygon(compareCoordinates);
// 判断是否在多边形内
return referencePolygon.contains(comparePolygon);
}
/**
* 判断两个平面是否相交
*
* @param referencePlane 参考平面
* @param comparePlane 待比较平面
* @return 平面是否相交
*/
public static Boolean arePolygonsIntersecting(List<List<Double>> referencePlane, List<List<Double>> comparePlane) {
// 构建 Polygon A参考面
Coordinate[] coordsA = referencePlane.stream()
.map(p -> new Coordinate(p.getFirst(), p.get(1)))
.toArray(Coordinate[]::new);
Polygon polygonA = geometryFactory.createPolygon(coordsA);
// 构建 Polygon B比较面
Coordinate[] coordsB = comparePlane.stream()
.map(p -> new Coordinate(p.getFirst(), p.get(1)))
.toArray(Coordinate[]::new);
Polygon polygonB = geometryFactory.createPolygon(coordsB);
// 使用 JTS 判断是否相交
return polygonA.intersects(polygonB);
}
/**
* 获取平面坐标数组
*
* @param planeLists 平面坐标列表
* @return 平面坐标数组
*/
public static Coordinate[] getPlaneCoordinate(List<List<Double>> planeLists) {
return planeLists.stream().map(planeList ->
new Coordinate(planeList.getFirst(), planeList.get(1)))
.toList().toArray(new Coordinate[0]);
}
/**
* 获取二维坐标
*
* @param geometry 几何对象
* @return 二维坐标
*/
public static List<List<Double>> getTwoDimensionalCoordinates(FacGeometry geometry) {
String type = geometry.getType();
List<Object> coordinates = geometry.getCoordinates();
return switch (type) {
case GeoJsonConstant.POINT -> throw new ServiceException("点位无法创建方阵", HttpStatus.BAD_REQUEST);
case GeoJsonConstant.LINE -> coordinates.stream()
.filter(obj -> obj instanceof List<?>)
.map(obj -> ((List<?>) obj).stream()
.filter(num -> num instanceof Number)
.map(num -> ((Number) num).doubleValue())
.collect(Collectors.toList()))
.collect(Collectors.toList());
case GeoJsonConstant.POLYGON -> coordinates.stream()
.filter(obj -> obj instanceof List<?>)
.flatMap(obj -> ((List<?>) obj).stream())
.filter(pointObj -> pointObj instanceof List<?>)
.map(pointObj -> ((List<?>) pointObj).stream()
.filter(num -> num instanceof Number)
.map(num -> ((Number) num).doubleValue())
.collect(Collectors.toList()))
.collect(Collectors.toList());
default -> throw new ServiceException("暂不支持该类型", HttpStatus.BAD_REQUEST);
};
}
/**
* 匹配最近的点,获取该点的名称
*
* @param polygon 平面
* @param points 点列表集合
* @return 最近点的名称
*/
public static String findNearestPointText(List<List<Double>> polygon, List<FacFeatureByPoint> points) {
if (polygon == null || polygon.size() < 3 || points == null || points.isEmpty()) {
return null;
}
// 1. 构建 Polygon
Coordinate[] polygonCoords = polygon.stream()
.map(coord -> new Coordinate(coord.getFirst(), coord.get(1)))
.toArray(Coordinate[]::new);
Polygon jtsPolygon = geometryFactory.createPolygon(polygonCoords);
// 2. 构建空间索引JTS STRtree
STRtree spatialIndex = new STRtree();
Map<Coordinate, FacFeatureByPoint> coordToFeatureMap = new HashMap<>();
for (FacFeatureByPoint feature : points) {
List<Double> coords = feature.getGeometry().getCoordinates();
if (coords == null || coords.size() != 2) continue;
Coordinate coord = new Coordinate(coords.get(0), coords.get(1));
Point point = geometryFactory.createPoint(coord);
// 用点的 Envelope 加入索引
spatialIndex.insert(point.getEnvelopeInternal(), point);
coordToFeatureMap.put(coord, feature);
}
// 3. 查询距离 polygon 最近的点
// 用 polygon 中心点附近构造 Envelope扩大一些范围
Envelope searchEnv = jtsPolygon.getEnvelopeInternal();
searchEnv.expandBy(10); // 扩大搜索半径
@SuppressWarnings("unchecked")
List<Point> candidatePoints = spatialIndex.query(searchEnv);
double minDistance = Double.MAX_VALUE;
Coordinate nearestCoord = null;
for (Point point : candidatePoints) {
double distance = point.distance(jtsPolygon);
if (distance < minDistance) {
minDistance = distance;
nearestCoord = point.getCoordinate();
}
}
if (nearestCoord != null) {
FacFeatureByPoint nearestFeature = coordToFeatureMap.get(nearestCoord);
if (nearestFeature != null && nearestFeature.getProperties() != null) {
return nearestFeature.getProperties().getText();
}
}
return null;
}
/**
* 匹配最近的面,获取该面的信息
*
* @param pointFeature 点位
* @param polygons 平面列表
* @return 最近面的信息
*/
public static FacFeatureByPlane findNearestOrContainingPolygon(
FacFeatureByPoint pointFeature,
List<FacFeatureByPlane> polygons
) {
if (pointFeature == null || polygons == null || polygons.isEmpty()) {
return null;
}
List<Double> coords = pointFeature.getGeometry().getCoordinates();
if (coords == null || coords.size() != 2) return null;
Coordinate pointCoord = new Coordinate(coords.get(0), coords.get(1));
Point point = geometryFactory.createPoint(pointCoord);
FacFeatureByPlane nearestPolygon = null;
double minDistance = Double.MAX_VALUE;
for (FacFeatureByPlane polygonFeature : polygons) {
if (polygonFeature == null || polygonFeature.getGeometry() == null) {
continue; // 跳过空对象
}
List<List<Double>> polyCoords = polygonFeature.getGeometry().getCoordinates().getFirst();
Coordinate[] polygonCoords = polyCoords.stream()
.map(c -> new Coordinate(c.getFirst(), c.get(1)))
.toArray(Coordinate[]::new);
Polygon polygon = geometryFactory.createPolygon(polygonCoords);
// 优先使用包含点的 polygon
if (polygon.contains(point)) {
return polygonFeature;
}
double distance = polygon.distance(point);
if (distance < minDistance) {
minDistance = distance;
nearestPolygon = polygonFeature;
}
}
return nearestPolygon;
}
}

View File

@ -0,0 +1,24 @@
package org.dromara.common.utils;
import cn.hutool.json.JSONArray;
/**
* @author lilemy
* @date 2025/5/30 14:55
*/
public class JsonDimensionUtil {
/**
* 判断 JSONArray 的嵌套维度
*
* @param array 需要判断的 JSONArray
* @return 返回维度层级1 表示一维2 表示二维3 表示三维 ...
*/
public static int getJsonArrayDepth(Object array) {
if (array instanceof JSONArray jsonArray && !jsonArray.isEmpty()) {
return 1 + getJsonArrayDepth(jsonArray.getFirst());
}
return 0;
}
}

View File

@ -0,0 +1,34 @@
package org.dromara.common.utils;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
/**
* @author lilemy
* @date 2025/5/28 11:25
*/
public class PageConvertUtil {
/**
* 将 Page<T> 转换为 Page<V>
*
* @param source 原始分页数据
* @param mapper 实体 -> VO 的转换函数
* @return Page<V>
*/
public static <T, V> Page<V> convert(Page<T> source, Function<T, V> mapper) {
Page<V> target = new Page<>(source.getCurrent(), source.getSize(), source.getTotal());
if (CollUtil.isEmpty(source.getRecords())) {
target.setRecords(Collections.emptyList());
} else {
List<V> voList = source.getRecords().stream().map(mapper).toList();
target.setRecords(voList);
}
return target;
}
}

View File

@ -0,0 +1,142 @@
package org.dromara.common.utils;
import cn.hutool.core.img.ImgUtil;
import cn.hutool.extra.qrcode.QrCodeUtil;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Image;
import com.itextpdf.text.pdf.PdfContentByte;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfStamper;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* @author lilemy
* @date 2025/7/7 9:56
*/
public class PdfBoxQrCodeGenerator {
/**
* 生成二维码图片并返回路径
*
* @param text 二维码文本
* @param width 二维码宽度
* @param height 二维码高度
* @param outputPath 二维码图片保存路径
* @return 二维码图片保存路径
*/
public static String generateQRCodeImage(String text, int width, int height, String outputPath) throws Exception {
BitMatrix bitMatrix = new MultiFormatWriter().encode(text, BarcodeFormat.QR_CODE, width, height);
MatrixToImageWriter.writeToPath(bitMatrix, "PNG", new File(outputPath).toPath());
return outputPath;
}
/**
* 获取二维码图片字节数组
*
* @param text 二维码文本
* @param width 二维码宽度
* @param height 二维码高度
* @return 二维码图片字节数组
*/
public static byte[] generateQRCodeBytes(String text, int width, int height) {
BufferedImage image = QrCodeUtil.generate(text, width, height);
ByteArrayOutputStream out = new ByteArrayOutputStream();
ImgUtil.write(image, ImgUtil.IMAGE_TYPE_PNG, out);
return out.toByteArray();
}
/**
* 获取二维码图片字节数组
*
* @param text 二维码文本
* @return 二维码图片字节数组
*/
public static byte[] generateQRCodeBytes(String text) {
return generateQRCodeBytes(text, 200, 200);
}
/**
* 在PDF指定位置添加二维码
*
* @param srcPdf 原PDF文件路径
* @param destPdf 新PDF文件路径
* @param qrImagePath 二维码图片路径
* @param pageNum 页码
* @param x 坐标
* @param y 坐标
*/
public static void addQRCodeToPDF(String srcPdf, String destPdf, String qrImagePath, int pageNum, float x, float y) throws IOException, DocumentException {
PdfReader reader = new PdfReader(srcPdf);
PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(destPdf));
PdfContentByte content = stamper.getOverContent(pageNum);
Image image = Image.getInstance(qrImagePath);
image.setAbsolutePosition(x, y); // 坐标:左下角为原点
image.scaleAbsolute(100, 100); // 设置二维码大小
content.addImage(image);
stamper.close();
reader.close();
}
/**
* 在PDF指定位置添加二维码
*
* @param srcPdf 原PDF文件路径
* @param destPdf 新PDF文件路径
* @param qrCodeBytes 二维码图片字节数组
* @param pageNum 页码
* @param x 坐标
* @param y 坐标
*/
public static void addQRCodeToPDF(String srcPdf, String destPdf, byte[] qrCodeBytes, int pageNum, float x, float y) throws IOException, DocumentException {
PdfReader reader = new PdfReader(srcPdf);
PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(destPdf));
PdfContentByte content = stamper.getOverContent(pageNum);
Image image = Image.getInstance(qrCodeBytes);
image.setAbsolutePosition(x, y); // 坐标:左下角为原点
image.scaleAbsolute(100, 100); // 设置二维码大小
content.addImage(image);
stamper.close();
reader.close();
}
/**
* 在PDF指定位置添加二维码并返回数据流
*
* @param srcPdf 原PDF文件路径
* @param qrCodeBytes 二维码图片字节数组
* @param pageNum 页码
* @param x 坐标
* @param y 坐标
* @return 插入二维码后的PDF文件流
*/
public static ByteArrayOutputStream addQRCodeToPDF(String srcPdf, byte[] qrCodeBytes, int pageNum, float x, float y) throws IOException, DocumentException {
PdfReader reader = new PdfReader(srcPdf);
ByteArrayOutputStream pdfOut = new ByteArrayOutputStream();
PdfStamper stamper = new PdfStamper(reader, pdfOut);
PdfContentByte content = stamper.getOverContent(pageNum);
Image image = Image.getInstance(qrCodeBytes);
image.setAbsolutePosition(x, y); // 坐标:左下角为原点
image.scaleAbsolute(100, 100); // 设置二维码大小
content.addImage(image);
stamper.close();
reader.close();
return pdfOut;
}
}

View File

@ -0,0 +1,101 @@
package org.dromara.common.utils.baiduUtil;
import com.google.gson.Gson;
import org.dromara.common.utils.baiduUtil.entity.AccessTokenResponse;
import org.glassfish.jaxb.runtime.v2.runtime.reflect.opt.Const;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Value;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.concurrent.TimeUnit;
import static org.dromara.common.constant.businessConstant.REDIS_BAIDU_KEY;
/**
* @Author 铁憨憨
* @Date 2025/7/18 9:46
* @Version 1.0
*
* 获取百度AccessToken
*/
@Service
public class BaiDuCommon {
@Autowired
private HttpClient httpClient;
@Autowired
private StringRedisTemplate redisTemplate;
@Value("${baidu.client.id}")
private String clientId;
@Value("${baidu.client.secret}")
private String clientSecret;
private static final String TOKEN_URL = "https://aip.baidubce.com/oauth/2.0/token?client_id=%s&client_secret=%s&grant_type=client_credentials";
private static final Gson gson = new Gson();
/**
* 获取百度AccessToken优先从Redis缓存中获取
*/
public String getAccessToken() {
// 1. 尝试从Redis中获取
String cachedToken = redisTemplate.opsForValue().get(REDIS_BAIDU_KEY);
if (cachedToken != null) {
return cachedToken;
}
// 2. Redis中没有加锁防止并发请求刷新Token
synchronized (this) {
// 再次检查,避免其他线程已经刷新
cachedToken = redisTemplate.opsForValue().get(REDIS_BAIDU_KEY);
if (cachedToken != null) {
return cachedToken;
}
// 3. 真正调用API获取新Token
String newToken = fetchAccessTokenFromBaidu();
if (newToken != null) {
// 4. 将新Token存入Redis并设置过期时间比实际有效期短一些
redisTemplate.opsForValue().set(REDIS_BAIDU_KEY, newToken, 29, TimeUnit.DAYS);
return newToken;
}
}
return null; // 获取失败
}
/**
* 从百度API获取AccessToken
*/
private String fetchAccessTokenFromBaidu() {
String url = String.format(TOKEN_URL, clientId, clientSecret);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.POST(HttpRequest.BodyPublishers.noBody())
.build();
try {
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 200) {
AccessTokenResponse tokenResponse = gson.fromJson(response.body(), AccessTokenResponse.class);
return tokenResponse.getAccessToken();
} else {
throw new IOException("获取AccessToken失败状态码: " + response.statusCode());
}
} catch (IOException | InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("调用百度API获取AccessToken失败", e);
}
}
}

View File

@ -0,0 +1,205 @@
package org.dromara.common.utils.baiduUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.dromara.common.utils.baiduUtil.entity.face.ComparisonRes;
import org.dromara.common.utils.baiduUtil.entity.face.HumanFaceReq;
import org.dromara.common.utils.baiduUtil.entity.face.HumanFaceRes;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.fasterxml.jackson.core.JsonProcessingException;
import java.io.IOException;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.List;
/**
* @Author 铁憨憨
* @Date 2025/7/18 17:09
* @Version 1.0
* 处理百度人脸相关逻辑:人脸检测、人脸对比
*/
@Service
public class BaiDuFace {
@Autowired
private BaiDuCommon baiDuCommon;
@Autowired
private HttpClient httpClient;
@Autowired
private ObjectMapper objectMapper;
// 人脸识别API地址
private static final String FACE_DETECT_URL = "https://aip.baidubce.com/rest/2.0/face/v3/detect?access_token=%s";
// 人脸对比API地址
private static final String FACE_COMPARE_URL = "https://aip.baidubce.com/rest/2.0/face/v3/match?access_token=%s";
/**
* 人脸识别+人脸检测
* @param request 人脸请求参数
* @throws RuntimeException 检测失败时抛出异常由Spring统一处理
*/
public void humanFace(HumanFaceReq request) {
// 1. 获取AccessToken
String accessToken = baiDuCommon.getAccessToken();
if (accessToken == null || accessToken.trim().isEmpty()) {
throw new RuntimeException("获取访问令牌失败token为空");
}
// 2. 构建请求URL
String requestUrl = String.format(FACE_DETECT_URL, URLEncoder.encode(accessToken, StandardCharsets.UTF_8));
try {
// 3. 序列化请求参数
String requestBody = objectMapper.writeValueAsString(request);
// 4. 构建HTTP请求
HttpRequest httpRequest = HttpRequest.newBuilder()
.uri(URI.create(requestUrl))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(requestBody))
.build();
// 5. 发送请求并获取响应
HttpResponse<String> response = httpClient.send(
httpRequest,
HttpResponse.BodyHandlers.ofString()
);
// 6. 解析响应结果
HumanFaceRes faceRep = objectMapper.readValue(response.body(), HumanFaceRes.class);
// 7. 处理API返回错误
if (faceRep.getErrorCode() != 0) {
throw new RuntimeException("错误码说明:" + faceRep.getErrorMsg());
}
// 8. 验证人脸信息(无人脸时抛出异常)
if (faceRep.getResult() == null
|| faceRep.getResult().getFaceList() == null
|| faceRep.getResult().getFaceList().isEmpty()) {
throw new RuntimeException("未检测到有效人脸信息");
}
// 9. 人脸质量校验
HumanFaceRes.FaceList faceInfo = faceRep.getResult().getFaceList().get(0);
// 9.1 人脸置信度校验必须为1.0
if (faceInfo.getFaceProbability() != 1.0) {
throw new RuntimeException("人脸置信度过低,请重新拍摄!");
}
// 9.2 真实人脸校验(排除卡通人脸)
HumanFaceRes.FaceType faceType = faceInfo.getFaceType();
if (faceType == null) {
throw new RuntimeException("无法判断人脸类型");
}
if ("cartoon".equals(faceType.getType())) {
throw new RuntimeException("请传入真实人脸,勿使用卡通图像");
}
// 校验真实人脸置信度需≥0.8
if (faceType.getProbability() < 0.8) {
throw new RuntimeException("人脸识别不清晰,请换个角度拍摄");
}
// 9.3 人脸模糊度校验模糊度≥0.1视为模糊)
HumanFaceRes.Quality quality = faceInfo.getQuality();
if (quality == null) {
throw new RuntimeException("无法获取人脸质量信息");
}
if (quality.getBlur() != 0) {
throw new RuntimeException("人脸过于模糊,请保持镜头稳定");
}
// 9.4 光线校验(光线值<100视为过暗
if (quality.getIllumination() < 100.0) {
throw new RuntimeException("光线太暗,请在光线充足的环境下拍摄");
}
// 9.5 人脸完整性校验1为完整0为不完整
if (quality.getCompleteness() != 1) {
throw new RuntimeException("人脸未完全显示,请确保面部在框内");
}
} catch (JsonProcessingException e) {
throw new RuntimeException("请求参数序列化失败:" + e.getMessage(), e);
} catch (IOException e) {
throw new RuntimeException("IO处理异常" + e.getMessage(), e);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 恢复中断状态
throw new RuntimeException("请求被中断:" + e.getMessage(), e);
}
}
/**
* 人脸对比(计算人脸相似度)
* @param requestList 人脸请求参数列表至少包含2个人脸
* @return 人脸相似度得分
* @throws RuntimeException 对比失败时抛出异常
*/
public double comparison(List<HumanFaceReq> requestList) {
// 1. 校验请求参数
if (requestList == null || requestList.size() < 2) {
throw new RuntimeException("人脸对比至少需要2张人脸图片");
}
// 2. 获取AccessToken
String accessToken = baiDuCommon.getAccessToken();
if (accessToken == null || accessToken.trim().isEmpty()) {
throw new RuntimeException("获取访问令牌失败token为空");
}
// 3. 构建请求URL
String requestUrl = String.format(FACE_COMPARE_URL, URLEncoder.encode(accessToken, StandardCharsets.UTF_8));
try {
// 4. 序列化请求参数
String requestBody = objectMapper.writeValueAsString(requestList);
// 5. 构建HTTP请求
HttpRequest httpRequest = HttpRequest.newBuilder()
.uri(URI.create(requestUrl))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(requestBody))
.build();
// 6. 发送请求并获取响应
HttpResponse<String> response = httpClient.send(
httpRequest,
HttpResponse.BodyHandlers.ofString()
);
// 7. 解析响应结果
ComparisonRes comparisonRep = objectMapper.readValue(response.body(), ComparisonRes.class);
// 8. 处理API返回错误
if (comparisonRep.getErrorCode() != 0) {
throw new RuntimeException("人脸对比失败:" + comparisonRep.getErrorMsg()
+ "(错误码:" + comparisonRep.getErrorCode() + "");
}
// 9. 校验对比结果
if (comparisonRep.getResult() == null) {
throw new RuntimeException("人脸对比结果为空");
}
return comparisonRep.getResult().getScore();
} catch (JsonProcessingException e) {
throw new RuntimeException("请求参数序列化失败:" + e.getMessage(), e);
} catch (IOException e) {
throw new RuntimeException("IO处理异常" + e.getMessage(), e);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("请求被中断:" + e.getMessage(), e);
}
}
}

View File

@ -0,0 +1,204 @@
package org.dromara.common.utils.baiduUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.dromara.common.utils.baiduUtil.entity.ocr.IDCardInfo;
import org.dromara.common.utils.baiduUtil.entity.ocr.OcrReq;
import org.dromara.common.utils.baiduUtil.entity.ocr.Result;
import org.dromara.common.utils.baiduUtil.entity.ocr.WordsResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
/**
* @Author 铁憨憨
* @Date 2025/7/18 10:45
* @Version 1.0
*
* 处理百度OCR相关逻辑身份证识别、银行卡识别
*/
@Service
public class BaiDuOCR {
@Autowired
private BaiDuCommon baiDuCommon;
@Autowired
private HttpClient httpClient;
@Autowired
private ObjectMapper objectMapper; //ObjectMapper 是 Java 处理 JSON 的核心工具,项目中使用它进行 JSON 与 Java 对象的相互转换。
String baseUrlTemplate = "https://aip.baidubce.com/rest/2.0/ocr/v1/bankcard?access_token=%s";
String BASE_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/idcard?access_token=%s";
/**
* @description 银行卡OCR识别
* @author 铁憨憨
* @date 2025/7/18 11:50
* @param vr 请求参数
* @return 识别结果Result
**/
public Result bankCardOCRRecognition(OcrReq vr) {
// 先从缓存里面捞取token若为空直接抛出异常
String atStr = baiDuCommon.getAccessToken();
if (atStr == null || atStr.trim().isEmpty()) {
throw new RuntimeException("获取访问令牌失败token为空");
}
try {
// 构建请求URL包含token参数
String requestUrl = String.format(baseUrlTemplate, URLEncoder.encode(atStr, StandardCharsets.UTF_8));
// 准备请求体将请求参数转为JSON
String requestBody = objectMapper.writeValueAsString(vr);
if (requestBody == null) {
throw new RuntimeException("请求参数序列化失败");
}
// 构建HTTP请求
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(requestUrl))
.header("Content-Type", "application/x-www-form-urlencoded")
.POST(HttpRequest.BodyPublishers.ofString(requestBody))
.build();
// 发送请求并获取响应
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
// 处理响应状态码非200直接抛出异常
if (response.statusCode() != 200) {
throw new RuntimeException("接口请求失败,状态码:" + response.statusCode() + ",响应信息:" + response.body());
}
// 解析响应结果(若解析失败抛出异常)
Result result = objectMapper.readValue(response.body(), Result.class);
if (result == null) {
throw new RuntimeException("响应结果解析失败");
}
// // 若接口返回错误码如百度API的error_code抛出异常
// if (result.getErrorCode() != null && result.getErrorCode() != 0) {
// throw new RuntimeException("接口返回错误:" + result.getErrorMessage() + "(错误码:" + result.getErrorCode() + "");
// }
return result;
} catch (IOException e) {
// IO异常如序列化失败、网络IO错误
throw new RuntimeException("IO处理异常" + e.getMessage(), e);
} catch (InterruptedException e) {
// 线程中断异常
Thread.currentThread().interrupt(); // 恢复中断状态
throw new RuntimeException("请求被中断:" + e.getMessage(), e);
}
}
/**
* @description 身份证OCR识别
* @author 铁憨憨
* @date 2025/7/18 16:53
* @param request 请求参数
* @return 识别结果WordsResult
**/
public WordsResult idCardOCR(OcrReq request) {
// 获取AccessToken为空直接抛异常
String accessToken = baiDuCommon.getAccessToken();
if (accessToken == null || accessToken.trim().isEmpty()) {
throw new RuntimeException("获取访问令牌失败token为空");
}
// 构建请求URL
String requestUrl = String.format(BASE_URL, accessToken);
try {
// 准备请求体(序列化失败抛异常)
String requestBody = objectMapper.writeValueAsString(request);
if (requestBody == null) {
throw new RuntimeException("请求参数序列化失败");
}
// 构建HTTP请求
HttpRequest httpRequest = HttpRequest.newBuilder()
.uri(URI.create(requestUrl))
.header("Content-Type", "application/x-www-form-urlencoded")
.POST(HttpRequest.BodyPublishers.ofString(requestBody))
.build();
// 发送请求并获取响应
HttpResponse<String> response = httpClient.send(
httpRequest,
HttpResponse.BodyHandlers.ofString()
);
// 处理响应状态码非200直接抛异常
if (response.statusCode() != 200) {
throw new RuntimeException("接口请求失败,状态码:" + response.statusCode() + ",响应信息:" + response.body());
}
// 处理响应内容(去除空格并解析)
String responseBody = response.body().replaceAll("\\s+", "");
IDCardInfo idCardInfo = objectMapper.readValue(responseBody, IDCardInfo.class);
if (idCardInfo == null) {
throw new RuntimeException("响应结果解析失败");
}
// 检查身份证状态状态异常由validateImageStatus抛异常
validateImageStatus(idCardInfo.getImageStatus());
// 提取识别结果(结果为空抛异常)
WordsResult wordsResult = idCardInfo.getWordsResult();
if (wordsResult == null) {
throw new RuntimeException("未识别到身份证信息");
}
return wordsResult;
} catch (IOException e) {
// IO异常序列化、网络IO等
throw new RuntimeException("IO处理异常" + e.getMessage(), e);
} catch (InterruptedException e) {
// 线程中断异常
Thread.currentThread().interrupt(); // 恢复中断状态
throw new RuntimeException("请求被中断:" + e.getMessage(), e);
}
}
/**
* @description 验证身份证图片状态
* @author 铁憨憨
* @date 2025/7/18 17:08
* @param imageStatus 图片状态
* @throws RuntimeException 当状态不正常时抛出运行时异常
**/
private void validateImageStatus(String imageStatus) {
if (imageStatus == null || "normal".equals(imageStatus)) {
return; // 正常状态,直接返回
}
// 根据不同状态码抛出不同的异常信息
String errorMessage = switch (imageStatus) {
case "reversed_side" -> "身份证正反面颠倒,请调整后重新上传";
case "non_idcard" -> "上传的图片中不包含身份证,请重新上传";
case "blurred" -> "身份证图片模糊,请重新上传清晰的图片";
case "other_type_card" -> "上传的不是身份证,请上传有效的身份证图片";
case "over_exposure" -> "身份证关键字段反光或过曝,请调整光线后重新上传";
case "over_dark" -> "身份证图片欠曝(亮度过低),请调整光线后重新上传";
default -> "未知错误:" + imageStatus;
};
throw new RuntimeException(errorMessage);
}
}

View File

@ -0,0 +1,19 @@
package org.dromara.common.utils.baiduUtil.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
/**
* @Author 铁憨憨
* @Date 2025/7/18 9:56
* @Version 1.0
*/
@Data
@Accessors(chain = true)
public class AccessTokenResponse {
private String accessToken;
private int expiresIn;
private String scope;
}

View File

@ -0,0 +1,49 @@
package org.dromara.common.utils.baiduUtil.entity.face;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.List;
/**
* @Author 铁憨憨
* @Date 2025/7/18 17:15
* @Version 1.0
*
* 人脸对比响应结果
*/
@Data
public class ComparisonRes {
@JsonProperty("error_code")
private int errorCode;
@JsonProperty("error_msg")
private String errorMsg;
@JsonProperty("log_id")
private long logId;
@JsonProperty("timestamp")
private long timestamp;
@JsonProperty("cached")
private int cached;
@JsonProperty("result")
private CompareResult result;
@Data
public static class CompareResult {
@JsonProperty("score")
private double score; // 人脸相似度得分
@JsonProperty("face_list")
private List<FaceToken> faceList;
}
@Data
public static class FaceToken {
@JsonProperty("face_token")
private String faceToken;
}
}

View File

@ -0,0 +1,23 @@
package org.dromara.common.utils.baiduUtil.entity.face;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
/**
* @Author 铁憨憨
* @Date 2025/7/18 17:12
* @Version 1.0
*
* 人脸识别请求参数
*/
@Data
public class HumanFaceReq {
@JsonProperty("image")
private String image; // 图片base64字符串
@JsonProperty("image_type")
private String imageType = "BASE64"; // 图片类型固定为BASE64
@JsonProperty("face_field")
private String faceField = "face_type,quality"; // 需要返回的人脸字段
}

View File

@ -0,0 +1,142 @@
package org.dromara.common.utils.baiduUtil.entity.face;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.List;
/**
* @Author 铁憨憨
* @Date 2025/7/18 17:13
* @Version 1.0
*
* 人脸识别响应结果
*/
@Data
public class HumanFaceRes {
@JsonProperty("error_code")
private int errorCode;
@JsonProperty("error_msg")
private String errorMsg;
@JsonProperty("log_id")
private long logId;
@JsonProperty("timestamp")
private long timestamp;
@JsonProperty("cached")
private int cached;
@JsonProperty("result")
private Result result;
@Data
public static class Result {
@JsonProperty("face_num")
private int faceNum;
@JsonProperty("face_list")
private List<FaceList> faceList;
}
@Data
public static class FaceList {
@JsonProperty("face_token")
private String faceToken;
@JsonProperty("location")
private Location location;
@JsonProperty("face_probability")
private double faceProbability;
@JsonProperty("angle")
private Angle angle;
@JsonProperty("face_type")
private FaceType faceType;
@JsonProperty("quality")
private Quality quality;
}
@Data
public static class Location {
@JsonProperty("left")
private double left;
@JsonProperty("top")
private double top;
@JsonProperty("width")
private int width;
@JsonProperty("height")
private int height;
@JsonProperty("rotation")
private int rotation;
}
@Data
public static class Angle {
@JsonProperty("yaw")
private double yaw;
@JsonProperty("pitch")
private double pitch;
@JsonProperty("roll")
private double roll;
}
@Data
public static class FaceType {
@JsonProperty("type")
private String type;
@JsonProperty("probability")
private double probability;
}
@Data
public static class Quality {
@JsonProperty("occlusion")
private Occlusion occlusion;
@JsonProperty("blur")
private double blur;
@JsonProperty("illumination")
private double illumination;
@JsonProperty("completeness")
private long completeness;
}
@Data
public static class Occlusion {
@JsonProperty("left_eye")
private double leftEye;
@JsonProperty("right_eye")
private double rightEye;
@JsonProperty("nose")
private double nose;
@JsonProperty("mouth")
private double mouth;
@JsonProperty("left_cheek")
private double leftCheek;
@JsonProperty("right_cheek")
private double rightCheek;
@JsonProperty("chin_contour")
private double chinContour;
}
}

View File

@ -0,0 +1,39 @@
package org.dromara.common.utils.baiduUtil.entity.ocr;
import com.alibaba.fastjson2.annotation.JSONField;
import lombok.Data;
/**
* @Author 铁憨憨
* @Date 2025/7/18 10:58
* @Version 1.0
*
* 银行卡具体信息
*/
@Data
//public class BankData {
// private String valid_date; // 有效期
// private String bank_card_number; // 卡号
// private String bank_name; // 银行名称
// private int bank_card_type; // 卡类型0未知1借记卡2贷记卡等
// private String holder_name; // 持卡人姓名(可能为空)
//}
public class BankData {
@JSONField(name = "bank_card_number",label = "银行卡卡号")
private String bankCardNumber;
@JSONField(name = "valid_date",label = "有效期")
private String validDate;
@JSONField(name = "bank_card_type",label = "银行卡类型0不能识别; 1借记卡; 2贷记卡原信用卡大部分为贷记卡; 3准贷记卡; 4预付费卡")
private int bankCardType;
@JSONField(name = "bank_name",label = "银行名,不能识别时为空")
private String bankName;
@JSONField(name = "holder_name",label = "持卡人姓名,不能识别时为空")
private String holderName;
}

View File

@ -0,0 +1,16 @@
package org.dromara.common.utils.baiduUtil.entity.ocr;
import lombok.Data;
/**
* @Author 铁憨憨
* @Date 2025/7/18 10:55
* @Version 1.0
*
* 单个识别字段(含文字和位置)
*/
@Data
public class Field {
private Location location; // 位置信息
private String words; // 识别文字
}

View File

@ -0,0 +1,30 @@
package org.dromara.common.utils.baiduUtil.entity.ocr;
import com.alibaba.fastjson2.annotation.JSONField;
import lombok.Data;
/**
* @Author 铁憨憨
* @Date 2025/7/18 10:50
* @Version 1.0
*
* 身份证识别结果
*/
@Data
public class IDCardInfo {
@JSONField(name = "words_result")
private WordsResult wordsResult; // 识别字段集合(姓名、身份证号等)
@JSONField(name = "words_result_num")
private int wordsResultNum; // 识别字段数量
@JSONField(name = "idcard_number_type")
private int idCardNumberType; // 身份证类型如18位/15位
private String imageStatus; // 图像状态(如是否颠倒)
@JSONField(name = "log_id")
private long logId; // 请求唯一标识日志ID
private String photo; // 身份证照片base64若检测
}

View File

@ -0,0 +1,18 @@
package org.dromara.common.utils.baiduUtil.entity.ocr;
import lombok.Data;
/**
* @Author 铁憨憨
* @Date 2025/7/18 10:56
* @Version 1.0
*
* 位置信息(坐标)
*/
@Data
public class Location {
private int top; // 顶部坐标
private int left; // 左侧坐标
private int width; // 宽度
private int height; // 高度
}

View File

@ -0,0 +1,24 @@
package org.dromara.common.utils.baiduUtil.entity.ocr;
import com.alibaba.fastjson2.annotation.JSONField;
import lombok.Data;
/**
* @Author 铁憨憨
* @Date 2025/7/18 10:47
* @Version 1.0
*
* OCR请求参数身份证/银行卡通用)
*/
@Data
public class OcrReq {
private String image; // 图像base64与url二选一
private String url; // 图像URL与image二选一
@JSONField(name = "id_card_side")
private String idCardSide; // 身份证正反面front/back银行卡无需
@JSONField(name = "detect_photo")
private boolean detectPhoto;// 是是否开启银行卡质量类型(清晰模糊、边框/四角不完整检测功能默认不开启false。
}

View File

@ -0,0 +1,22 @@
package org.dromara.common.utils.baiduUtil.entity.ocr;
import com.alibaba.fastjson2.annotation.JSONField;
import lombok.Data;
/**
* @Author 铁憨憨
* @Date 2025/7/18 11:20
* @Version 1.0
*
* 银行卡识别的完整返回结果
*/
@Data
public class Result {
@JSONField(name = "result")
private BankData res;
private int direction;
@JSONField(name = "log_id")
private long logId;
}

View File

@ -0,0 +1,33 @@
package org.dromara.common.utils.baiduUtil.entity.ocr;
import com.alibaba.fastjson2.annotation.JSONField;
import lombok.Data;
/**
* @Author 铁憨憨
* @Date 2025/7/18 10:50
* @Version 1.0
*
* 身份证识别字段集合
*/
@Data
public class WordsResult {
@JSONField(name = "姓名")
private Field name; // 姓名
@JSONField(name = "民族")
private Field nation; // 民族
@JSONField(name = "住址")
private Field address; // 住址
@JSONField(name = "公民身份号码")
private Field citizenIdentification; // 身份证号
@JSONField(name = "出生")
private Field birth; // 出生日期
@JSONField(name = "性别")
private Field gender; // 性别
@JSONField(name = "失效日期")
private Field expirationDate; // 失效日期
@JSONField(name = "签发机关")
private Field issuingAuthority; // 签发机关
@JSONField(name = "签发日期")
private Field issueDate; // 签发日期
}

Some files were not shown because too many files have changed in this diff Show More