diff --git a/src/main/java/net/geedge/asw/common/config/Query.java b/src/main/java/net/geedge/asw/common/config/Query.java new file mode 100644 index 0000000..829c9fb --- /dev/null +++ b/src/main/java/net/geedge/asw/common/config/Query.java @@ -0,0 +1,101 @@ +package net.geedge.asw.common.config; + +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.core.metadata.OrderItem; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import net.geedge.asw.common.util.ASWException; +import net.geedge.asw.common.util.Constants; +import net.geedge.asw.common.util.RCode; +import net.geedge.asw.common.util.T; + +import java.util.HashMap; +import java.util.Map; + +/** + * 查询参数 + */ +public class Query { + + private Class clz; + + public Class getClz() { + return clz; + } + + public void setClz(Class clz) { + this.clz = clz; + } + + public Query(Class clz) { + this.clz = clz; + } + + public Page getPage(Map params) { + return this.getPage(params, null, false); + } + + public Page getPage(Map params, String defaultOrderField, boolean isAsc) { + //分页参数 + long curPage = 1; + long limit = Constants.PAGESIZE; + + if(params.get(Constants.PAGE) != null){ + curPage = Long.parseLong((String)params.get(Constants.PAGE)); + } + if(params.get(Constants.LIMIT) != null){ + limit = Long.parseLong((String)params.get(Constants.LIMIT)); + if(limit == -1){ + limit = Long.MAX_VALUE; + curPage = 0; + } + } + + //分页对象 + Page page = new Page(curPage, limit); + + //分页参数 + params.put(Constants.PAGE, page); + + //排序字段 orderBy=id + //防止SQL注入(因为sidx、order是通过拼接SQL实现排序的,会有SQL注入风险) + String orderField = SQLFilter.sqlInject((String)params.get(Constants.ORDER)); + + if (StrUtil.isNotEmpty(orderField)) { + boolean matcheFlag = orderField.trim().matches("-?[a-zA-Z_.-]+"); + if (!matcheFlag) { + throw new ASWException(RCode.ERROR); + } + + // 获取表名 + Class clz = this.getClz(); + String tableName = ""; + if (clz != null) { + TableName table = this.getClz().getAnnotation(TableName.class); + tableName = table.value(); + } + // 通过表名获取排序字段映射 + Map columnAliasMap = Constants.TABLE_NAME_ORDER_FIELD_MAPPING.get(tableName); + columnAliasMap = T.MapUtil.isEmpty(columnAliasMap) ? new HashMap<>():columnAliasMap; + if (orderField.startsWith("-")) { + orderField = orderField.substring(1, orderField.length()); + orderField = columnAliasMap.get(orderField) != null ? columnAliasMap.get(orderField) : orderField; + return page.addOrder(OrderItem.desc(orderField)); + } else { + orderField = columnAliasMap.get(orderField) != null ? columnAliasMap.get(orderField) : orderField; + return page.addOrder(OrderItem.asc(orderField)); + } + } + + // 默认排序 + if (StrUtil.isNotEmpty(defaultOrderField)) { + if (isAsc) { + return page.addOrder(OrderItem.asc(defaultOrderField)); + } else { + return page.addOrder(OrderItem.desc(defaultOrderField)); + } + } + + return page; + } +} diff --git a/src/main/java/net/geedge/asw/common/config/SQLFilter.java b/src/main/java/net/geedge/asw/common/config/SQLFilter.java new file mode 100644 index 0000000..66b5688 --- /dev/null +++ b/src/main/java/net/geedge/asw/common/config/SQLFilter.java @@ -0,0 +1,49 @@ +/** + * + */ + +package net.geedge.asw.common.config; + +import net.geedge.asw.common.util.ASWException; +import net.geedge.asw.common.util.RCode; +import net.geedge.asw.common.util.T; + +import java.util.regex.Pattern; + +/** + * SQL过滤 + * + * @author Mark sunlightcs@gmail.com + */ +public class SQLFilter { + + private static String reg = "(?:')|(?:--)|(/\\*(?:.|[\\n\\r])*?\\*/)|(\\b(select|update|union|and|or|delete|insert|trancate|char|into|substr|ascii|declare|exec|count|master|into|drop|execute)\\b)"; + + private static Pattern sqlPattern = Pattern.compile(reg, Pattern.CASE_INSENSITIVE); + + /** + * SQL注入过滤 + * @param str 待验证的字符串 + */ + public static String sqlInject(String str) { + if (T.StrUtil.isBlank(str)) { + return null; + } + + //转换成小写 + String str1 = str.toLowerCase(); + + String s = ""; + if (str1.startsWith("-")) { + s = str1.substring(1); + } else { + s = str1; + } + + if (sqlPattern.matcher(s).matches()) { + throw new ASWException(RCode.ERROR); + } + + return str; + } +} diff --git a/src/main/java/net/geedge/asw/common/util/Constants.java b/src/main/java/net/geedge/asw/common/util/Constants.java index 40cace2..27242cd 100644 --- a/src/main/java/net/geedge/asw/common/util/Constants.java +++ b/src/main/java/net/geedge/asw/common/util/Constants.java @@ -1,7 +1,9 @@ package net.geedge.asw.common.util; import java.io.File; +import java.util.HashMap; import java.util.List; +import java.util.Map; public class Constants { @@ -26,4 +28,31 @@ public class Constants { */ public static final List VISIBILITY_LIST = T.ListUtil.of("public", "private"); + /** + * 当前页码 + */ + public static final String PAGE = "current"; + /** + * 每页显示记录数 + */ + public static final String LIMIT = "size"; + /** + * 每页显示条数 + */ + public static final long PAGESIZE = 20; + /** + * 排序方式 + */ + public static final String ORDER = "orderBy"; + /** + * 表名 和 排序字段对应关系 KEY: tablename + */ + public static final Map> TABLE_NAME_ORDER_FIELD_MAPPING = T.MapUtil.newHashMap(); + + static { + Map applicationOrderFieldMap = new HashMap<>(); + TABLE_NAME_ORDER_FIELD_MAPPING.put("application", applicationOrderFieldMap); + } + + } diff --git a/src/main/java/net/geedge/asw/common/util/RCode.java b/src/main/java/net/geedge/asw/common/util/RCode.java index 13a2510..92ba8cc 100644 --- a/src/main/java/net/geedge/asw/common/util/RCode.java +++ b/src/main/java/net/geedge/asw/common/util/RCode.java @@ -26,6 +26,8 @@ public enum RCode { SYS_ACCESS_LEVEL_CANNOT_EMPTY(100014, "accessLevel cannot be empty"), SYS_WORKSPACE_ROLES_CANNOT_EMPTY(100015, "workspaceRoles cannot be empty"), SYS_USER_BUILT_IN(100016, "Built-in user are not allowed to delete or update"), + SYS_ROLE_BUILT_IN(100017, "Built-in role are not allowed to delete or update"), + SYS_ROLE_NOT_DELETE(100018, "Used role cannot be deleted"), // Application diff --git a/src/main/java/net/geedge/asw/module/sys/controller/SysRoleController.java b/src/main/java/net/geedge/asw/module/sys/controller/SysRoleController.java index 5afe814..8c4782b 100644 --- a/src/main/java/net/geedge/asw/module/sys/controller/SysRoleController.java +++ b/src/main/java/net/geedge/asw/module/sys/controller/SysRoleController.java @@ -1,6 +1,8 @@ package net.geedge.asw.module.sys.controller; +import cn.dev33.satoken.stp.StpUtil; import cn.hutool.log.Log; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import net.geedge.asw.common.util.ASWException; @@ -9,61 +11,90 @@ import net.geedge.asw.common.util.RCode; import net.geedge.asw.common.util.T; import net.geedge.asw.module.sys.entity.SysRoleEntity; import net.geedge.asw.module.sys.service.ISysRoleService; +import net.geedge.asw.module.workspace.entity.WorkspaceMemberEntity; +import net.geedge.asw.module.workspace.service.IWorkspaceMemberService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; +import java.util.List; +import java.util.Map; + @RestController @RequestMapping("/api/v1/role") public class SysRoleController { private static final Log log = Log.get(); + @Autowired private ISysRoleService roleService; + @Autowired + private IWorkspaceMemberService workspaceMemberService; + @GetMapping("/{id}") public R detail(@PathVariable("id") String id) { - SysRoleEntity entity = roleService.getById(id); + SysRoleEntity entity = roleService.detail(id); return R.ok().putData("record", entity); } @GetMapping - public R list(String ids, String name, - @RequestParam(defaultValue = "1") Integer current, - @RequestParam(defaultValue = "20") Integer size, - @RequestParam(defaultValue = "name") String orderBy) { - QueryWrapper queryWrapper = new QueryWrapper(); - queryWrapper.like(T.StrUtil.isNotBlank(name), "name", name).in(T.StrUtil.isNotBlank(ids), "id", ids.split(",")); - Page page = Page.of(current, size); - page.addOrder(T.PageUtil.decodeOrderByStr(orderBy)); - page = roleService.page(page, queryWrapper); + public R list(@RequestParam Map params) { + Page page = roleService.queryList(params); return R.ok(page); } @PostMapping public R add(@RequestBody SysRoleEntity entity) { - T.VerifyUtil.is(entity).notNull().and(entity.getName()).notEmpty(RCode.NAME_CANNOT_EMPTY); + T.VerifyUtil.is(entity).notNull() + .and(entity.getName()).notEmpty(RCode.NAME_CANNOT_EMPTY); SysRoleEntity one = roleService.getOne(new QueryWrapper().lambda().eq(SysRoleEntity::getName, entity.getName())); if (T.ObjectUtil.isNotNull(one)) { throw ASWException.builder().rcode(RCode.SYS_DUPLICATE_RECORD).build(); } entity.setCreateTimestamp(T.DateUtil.current()); + entity.setCreateUserId(StpUtil.getLoginIdAsString()); + entity.setUpdateTimestamp(T.DateUtil.current()); + entity.setUpdateUserId(StpUtil.getLoginIdAsString()); roleService.saveOrUpdateRole(entity); return R.ok().putData("id", entity.getId()); } @PutMapping public R update(@RequestBody SysRoleEntity entity) { - T.VerifyUtil.is(entity).notNull().and(entity.getId()).notEmpty(RCode.ID_CANNOT_EMPTY); - entity.setCreateTimestamp(null); + T.VerifyUtil.is(entity).notNull() + .and(entity.getId()).notEmpty(RCode.ID_CANNOT_EMPTY) + .and(entity.getName()).notEmpty(RCode.NAME_CANNOT_EMPTY); + + SysRoleEntity one = roleService.getOne(new QueryWrapper().lambda().eq(SysRoleEntity::getName, entity.getName())); + SysRoleEntity role = roleService.getById(entity.getId()); + if (role.getBuildIn() == 1) { + throw ASWException.builder().rcode(RCode.SYS_ROLE_BUILT_IN).build(); + } + if (T.ObjectUtil.isNotNull(one) && !one.getId().equals(entity.getId())) { + throw ASWException.builder().rcode(RCode.SYS_DUPLICATE_RECORD).build(); + } + + entity.setUpdateTimestamp(T.DateUtil.current()); + entity.setUpdateUserId(StpUtil.getLoginIdAsString()); roleService.saveOrUpdateRole(entity); return R.ok().putData("id", entity.getId()); } @DeleteMapping - public R delete(String[] ids) { + public R delete(String ids) { T.VerifyUtil.is(ids).notEmpty(); - roleService.removeBatchByIds(T.ListUtil.of(ids)); - log.info("delete Role, ids: {}", T.ArrayUtil.toString(ids)); + List idList = T.ListUtil.of(ids.split(",")); + List roleList = roleService.list(new LambdaQueryWrapper().eq(SysRoleEntity::getBuildIn, 1).in(SysRoleEntity::getId, idList)); + if (T.CollectionUtil.isNotEmpty(roleList)) { + throw ASWException.builder().rcode(RCode.SYS_ROLE_BUILT_IN).build(); + } + + List list = workspaceMemberService.list(new LambdaQueryWrapper().in(WorkspaceMemberEntity::getRoleId, idList)); + if (T.CollectionUtil.isNotEmpty(list)) { + throw ASWException.builder().rcode(RCode.SYS_ROLE_NOT_DELETE).build(); + } + roleService.delete(idList); + log.info("Delete Role, ids: {}", T.ArrayUtil.toString(idList)); return R.ok(); } diff --git a/src/main/java/net/geedge/asw/module/sys/dao/SysRoleDao.java b/src/main/java/net/geedge/asw/module/sys/dao/SysRoleDao.java index f0be82f..092920a 100644 --- a/src/main/java/net/geedge/asw/module/sys/dao/SysRoleDao.java +++ b/src/main/java/net/geedge/asw/module/sys/dao/SysRoleDao.java @@ -1,7 +1,9 @@ package net.geedge.asw.module.sys.dao; import java.util.List; +import java.util.Map; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Select; @@ -15,6 +17,8 @@ public interface SysRoleDao extends BaseMapper { - @Select("select sm.* from sys_menu sm LEFT JOIN sys_role_menu srm on sm.id = srm.menu_id LEFT JOIN sys_user_role sur on srm.role_id = sur.role_id where sur.role_id = #{roleId} and sm.state = 1 order by sm.order") + @Select("select sm.* from sys_menu sm LEFT JOIN sys_role_menu srm on sm.id = srm.menu_id where srm.role_id = #{roleId} and sm.state = 1 order by sm.order") public List findMenuByRoleId(String roleId); + + List queryList(Page page, Map params); } diff --git a/src/main/java/net/geedge/asw/module/sys/entity/SysRoleEntity.java b/src/main/java/net/geedge/asw/module/sys/entity/SysRoleEntity.java index 9a449e1..82dc467 100644 --- a/src/main/java/net/geedge/asw/module/sys/entity/SysRoleEntity.java +++ b/src/main/java/net/geedge/asw/module/sys/entity/SysRoleEntity.java @@ -15,15 +15,35 @@ public class SysRoleEntity { @TableId(type = IdType.ASSIGN_UUID) private String id; + private String name; + private String i18n; + private String remark; + private Integer buildIn; + + private Long createTimestamp; + + private Long updateTimestamp; + + private String createUserId; + + private String updateUserId; + @TableField(exist = false) private String[] menuIds; - private Long createTimestamp; + @TableField(exist = false) private List menus; + @TableField(exist = false) private List buttons; + + @TableField(exist = false) + private SysUserEntity createUser; + + @TableField(exist = false) + private SysUserEntity updateUser; } diff --git a/src/main/java/net/geedge/asw/module/sys/service/ISysRoleService.java b/src/main/java/net/geedge/asw/module/sys/service/ISysRoleService.java index 7e25c23..ce01a12 100644 --- a/src/main/java/net/geedge/asw/module/sys/service/ISysRoleService.java +++ b/src/main/java/net/geedge/asw/module/sys/service/ISysRoleService.java @@ -1,10 +1,20 @@ package net.geedge.asw.module.sys.service; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.IService; import net.geedge.asw.module.sys.entity.SysRoleEntity; +import java.util.List; +import java.util.Map; + public interface ISysRoleService extends IService{ - public void saveOrUpdateRole(SysRoleEntity entity); + void saveOrUpdateRole(SysRoleEntity entity); + + SysRoleEntity detail(String id); + + Page queryList(Map params); + + void delete(List idList); } diff --git a/src/main/java/net/geedge/asw/module/sys/service/impl/SysRoleServiceImpl.java b/src/main/java/net/geedge/asw/module/sys/service/impl/SysRoleServiceImpl.java index 4a283a2..a87b274 100644 --- a/src/main/java/net/geedge/asw/module/sys/service/impl/SysRoleServiceImpl.java +++ b/src/main/java/net/geedge/asw/module/sys/service/impl/SysRoleServiceImpl.java @@ -1,8 +1,17 @@ package net.geedge.asw.module.sys.service.impl; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; import java.util.stream.Stream; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import net.geedge.asw.common.config.Query; +import net.geedge.asw.module.sys.entity.SysMenuEntity; +import net.geedge.asw.module.sys.service.ISysUserService; +import net.geedge.asw.module.workspace.entity.WorkspaceMemberEntity; +import net.geedge.asw.module.workspace.service.IWorkspaceMemberService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -23,11 +32,17 @@ public class SysRoleServiceImpl extends ServiceImpl i @Autowired private ISysRoleMenuService roleMenuService; + @Autowired + private ISysUserService userService; + + @Autowired + private IWorkspaceMemberService workspaceMemberService; + @Override @Transactional public void saveOrUpdateRole(SysRoleEntity entity) { // 删除 sys_role_menu 关联数据 - if (T.StrUtil.isBlank(entity.getId())) { + if (!T.StrUtil.isBlank(entity.getId())) { roleMenuService.remove(new QueryWrapper().eq("role_id", entity.getId())); } // 保存 role @@ -40,4 +55,50 @@ public class SysRoleServiceImpl extends ServiceImpl i roleMenuService.saveBatch(rmList); } + @Override + public SysRoleEntity detail(String id) { + SysRoleEntity role = this.getById(id); + // 组织 button 数据 + List menuList = this.getBaseMapper().findMenuByRoleId(id); + //生成 menu tree结构 + Map> groupMap = menuList.stream() + .filter(menu -> !T.StrUtil.equalsIgnoreCase(menu.getPid(), "0")) + .collect(Collectors.groupingBy(SysMenuEntity::getPid)); + + menuList.forEach(menu -> { + menu.setChildren(groupMap.get(menu.getId())); + }); + + List collect = menuList.stream() + .filter(menu -> T.StrUtil.equals(menu.getPid(), "0")) + .filter(menu -> T.StrUtil.equals(menu.getType(), "menu")) + .collect(Collectors.toList()); + role.setMenus(collect); + role.setCreateUser(userService.getById(role.getCreateUserId())); + role.setUpdateUser(userService.getById(role.getUpdateUserId())); + return role; + } + + @Override + public Page queryList(Map params) { + params.put("orderBy", T.StrUtil.emptyToDefault(T.MapUtil.getStr(params, "orderBy"), "name")); + Page page = new Query(SysRoleEntity.class).getPage(params); + List roleList = this.getBaseMapper().queryList(page, params); + page.setRecords(roleList); + return page; + } + + @Override + @Transactional() + public void delete(List idList) { + // remove role + this.removeBatchByIds(idList); + + // remove role menu 关联关系 + roleMenuService.remove(new LambdaQueryWrapper().in(SysRoleMenuEntity::getRoleId, idList)); + + // remove workspace member + workspaceMemberService.remove(new LambdaQueryWrapper().in(WorkspaceMemberEntity::getRoleId, idList)); + } + } diff --git a/src/main/resources/db/mapper/sys/SysRoleMapper.xml b/src/main/resources/db/mapper/sys/SysRoleMapper.xml new file mode 100644 index 0000000..8f569ef --- /dev/null +++ b/src/main/resources/db/mapper/sys/SysRoleMapper.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/db/migration/R__AZ_sys_i18n.sql b/src/main/resources/db/migration/R__AZ_sys_i18n.sql index b5e594e..255238f 100644 --- a/src/main/resources/db/migration/R__AZ_sys_i18n.sql +++ b/src/main/resources/db/migration/R__AZ_sys_i18n.sql @@ -117,5 +117,9 @@ INSERT INTO `sys_i18n`(`id`, `name`, `code`, `value`, `lang`, `remark`, `update_ INSERT INTO `sys_i18n`(`id`, `name`, `code`, `value`, `lang`, `remark`, `update_user_id`, `update_timestamp`) VALUES (191, '201015', 'APP_ATTACHMENT_NOT_EXIST', '应用附件不存在', 'zh', '', 'admin', 1724030366000); INSERT INTO `sys_i18n`(`id`, `name`, `code`, `value`, `lang`, `remark`, `update_user_id`, `update_timestamp`) VALUES (193, '201016', 'APP_PROPERTIES_FORMAT_ERROR', 'application properties format error', 'en', '', 'admin', 1724030366000); INSERT INTO `sys_i18n`(`id`, `name`, `code`, `value`, `lang`, `remark`, `update_user_id`, `update_timestamp`) VALUES (195, '201016', 'APP_PROPERTIES_FORMAT_ERROR', '应用属性格式错误', 'zh', '', 'admin', 1724030366000); +INSERT INTO `sys_i18n`(`id`, `name`, `code`, `value`, `lang`, `remark`, `update_user_id`, `update_timestamp`) VALUES (197, '100017', 'SYS_ROLE_BUILT_IN', 'Built-in role are not allowed to delete or update', 'en', '', 'admin', 1724030366000); +INSERT INTO `sys_i18n`(`id`, `name`, `code`, `value`, `lang`, `remark`, `update_user_id`, `update_timestamp`) VALUES (199, '100017', 'SYS_ROLE_BUILT_IN', '内置权限不能删除或修改', 'zh', '', 'admin', 1724030366000); +INSERT INTO `sys_i18n`(`id`, `name`, `code`, `value`, `lang`, `remark`, `update_user_id`, `update_timestamp`) VALUES (201, '100018', 'SYS_ROLE_NOT_DELETE', 'Used role cannot be delete', 'en', '', 'admin', 1724030366000); +INSERT INTO `sys_i18n`(`id`, `name`, `code`, `value`, `lang`, `remark`, `update_user_id`, `update_timestamp`) VALUES (203, '100018', 'SYS_ROLE_NOT_DELETE', '已使用权限不能删除', 'zh', '', 'admin', 1724030366000); SET FOREIGN_KEY_CHECKS = 1; diff --git a/src/main/resources/db/migration/V1.0.01__INIT_TABLES.sql b/src/main/resources/db/migration/V1.0.01__INIT_TABLES.sql index 1703503..f423839 100644 --- a/src/main/resources/db/migration/V1.0.01__INIT_TABLES.sql +++ b/src/main/resources/db/migration/V1.0.01__INIT_TABLES.sql @@ -34,24 +34,27 @@ DROP TABLE IF EXISTS `sys_role`; CREATE TABLE `sys_role` ( `id` varchar(64) NOT NULL, `name` varchar(255) NOT NULL, - `i18n` varchar(255) NOT NULL, - `remark` varchar(255) NOT NULL, + `i18n` varchar(255) NOT NULL DEFAULT '', + `remark` varchar(255) NOT NULL DEFAULT '', `build_in` int(10) NOT NULL DEFAULT 0, `create_timestamp` bigint(20) NOT NULL, + `update_timestamp` bigint(20) NOT NULL, + `create_user_id` varchar(64) NOT NULL, + `update_user_id` varchar(64) NOT NULL, PRIMARY KEY (`id`), KEY `idx_name` (`name`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 添加内置角色 -INSERT INTO `sys_role` (`id`, `name`, `i18n`, `remark`, `build_in`, `create_timestamp`) VALUES ('admin', 'admin', 'admin', 'admin', 1, UNIX_TIMESTAMP(NOW())*1000); -INSERT INTO `sys_role` (`id`, `name`, `i18n`, `remark`, `build_in`, `create_timestamp`) VALUES ('readonly', 'readonly', 'readonly', 'readonly', 1, UNIX_TIMESTAMP(NOW())*1000); +INSERT INTO `sys_role` (`id`, `name`, `i18n`, `remark`, `build_in`, `create_timestamp`, `update_timestamp`, `create_user_id`, `update_user_id`) VALUES ('admin', 'admin', 'admin', 'The system built-in administrator role', 1, UNIX_TIMESTAMP(NOW())*1000, UNIX_TIMESTAMP(NOW())*1000, 'admin', 'admin'); +INSERT INTO `sys_role` (`id`, `name`, `i18n`, `remark`, `build_in`, `create_timestamp`, `update_timestamp`, `create_user_id`, `update_user_id`) VALUES ('readonly', 'readonly', 'readonly', 'The system built-in common user role', 1, UNIX_TIMESTAMP(NOW())*1000, UNIX_TIMESTAMP(NOW())*1000, 'admin', 'admin'); DROP TABLE IF EXISTS `sys_menu`; CREATE TABLE `sys_menu` ( `id` varchar(64) NOT NULL, `name` varchar(255) NOT NULL, - `i18n` varchar(255) NOT NULL, + `i18n` varchar(255) NOT NULL DEFAULT '', `pid` varchar(64) NOT NULL DEFAULT '', `type` varchar(64) NOT NULL, `perms` varchar(255) NOT NULL DEFAULT '',