若依(芋道)框架完善记录

版本:芋道源码 2022年4月15日

由于芋道基于若依修改,所以此处改动可能也适用于若依。

修正系统bug

多租户模式无法登录

前端login.vue增加tenantId !== null判断:

validator: (rule, value, callback) => {
    // debugger
    getTenantIdByName(value).then((res) => {
    const tenantId = res.data
    // 修改下面代码
    if (tenantId !== null && tenantId >= 0) {
        // 设置租户
        Cookies.set('tenantId', tenantId)
        callback()
    } else {
        callback('租户不存在')
    }
    })
},

前端标签页切换后,无法保存标签页状态

表结构调整

system_menu表增加一个字段:

alter table map.system_menu add component_name varchar(255);

同时在sql.vm增加相应字段。

后端调整

MenuDO.java增加

/**
 * 组件名称
 */
private String componentName;

AuthMenuRespVO.java增加

@ApiModelProperty(value = "组件名称")
private String componentName;

MenuBaseVO.java增加

@ApiModelProperty(value = "组件名称", example = "post")
@Size(max = 255, message = "组件路径不能超过255个字符")
private String componentName;

前端调整

store/modules/permission.js,修改filterAsyncRouter函数:

function filterAsyncRouter() {
  // ...
  route.meta = {
    title: route.name,
    icon: route.icon,
    // 下方增加新代码
    componentName: route.componentName  
  }
  // ...
}

store/modules/tagsView.js增加代码:

ADD_CACHED_VIEW: (state, view) => {
  if (state.cachedViews.includes(view.meta.componentName)) return
  if (!view.meta.noCache) {
    state.cachedViews.push(view.meta.componentName)
  }
},

DEL_CACHED_VIEW: (state, view) => {
  const index = state.cachedViews.indexOf(view.meta.componentName)
  index > -1 && state.cachedViews.splice(index, 1)
},

system/menu/index.vue增加两个输入框:

...
<el-table ...>
  <!-- 权限标识下方增加 -->
  <el-table-column
    prop="componentName"
    label="组件名称"
    :show-overflow-tooltip="true"
  />
...
...
<!-- 添加或修改菜单对话框 -->
<!-- 菜单名称下增加 -->
<el-col :span="12">
  <el-form-item label="组件名称" prop="componentName">
    <el-input
      v-model="form.componentName"
      placeholder="请输入组件名称"
    />
  </el-form-item>
</el-col>
...

功能调整

修改完成后,菜单管理的对话框中会多出一个“组件名称”输入框,将其设置为Vue组件的name即可:

export default {
  name: '与此处name保持一致',
  // ...
}

svg-icon图标颜色问题

定位到src/assets/icons/svg,通过正则搜索所有svg中的fill=".*?",删除。

字体尺寸错误问题

定位到src/components/bpmnProcessDesigner/package/theme/process-designer.scss,将其中的

@import "~bpmn-js-token-simulation/assets/css/normalize.css";

删除。

标签页“关闭左侧”不起作用

定位到store/modules/tagsView.js,可以发现里面只有delRightTags,缺少delLeftTags,将其补全即可。

生成代码中出现编译错误

如果有DECIMAL类型字段,会出现编译错误,因为未import BigDecimal类型,将其补全即可。

修改src/resources/codegen/java/dal/do.vm:

import java.math.BigDecimal;

再修改src/resources/codegen/java/controller/vo中的每个vm:

import java.math.BigDecimal;

代码质量优化

.gitignore

使用工具重新生成

前端jsconfig.json

{
  "compilerOptions": {
    "baseUrl": "./",
    "paths": {
      "@/*": ["src/*"]
    },
    "target": "ES2015",
    "lib": ["ES2021"]
  },
  "include": ["./src/**/*"],
  "exclude": ["dist", "node_modules"]
}

前端代码格式化

使用eslint和prettier批量格式化,保持代码整洁

清理console.log

使用查找功能搜索代码中的console.log,将其删除。

request超时

找到request.js,增加超时时间的判断:

// 创建axios实例
const service = axios.create({
  // axios中请求配置有baseURL选项,表示请求URL公共部分
  baseURL: process.env.VUE_APP_BASE_API + '/admin-api/', // 此处的 /admin-api/ 地址,原因是后端的基础路径为 /admin-api/
  // 超时
  timeout: process.env.NODE_ENV === 'development' ? 1000000 : 10000
})

功能完善

提高系统响应速度

将动画速度调快,或者直接删除,从而提高感官上的响应速度。

前端index.html

把转圈动画直接删掉

<div id="loader-wrapper">
  <div id="loader"></div>
  <div class="loader-section section-left"></div>
  <div class="loader-section section-right"></div>
  <div class="load_title">正在加载系统资源,请耐心等待</div>
</div>

前端transition.scss

transition: all .5s;

全部替换成

transition: all .1s;

增加:

.el-loading-spinner .circular {
  -webkit-animation: loading-rotate 0.25s linear infinite;
  animation: loading-rotate 0.25s linear infinite;
}

加快按钮的转圈速度。

字典数据项维护

定位到views/system/dict/index.vue,增加按钮:

<el-button
  v-hasPermi="['system:dict:update']"
  size="mini"
  type="text"
  icon="el-icon-notebook-2"
  @click="$router.push('/dict/type/data/' + scope.row.id)"
>
  数据项
</el-button>

完善系统报错与日志查询

前端报错提示优化

修改request.js,根据实际情况调整报错内容,例如:

  • 会话超时:您长时间未操作,已自动注销,请重新登录
  • 网络异常:网络连接错误,请检查网络连接,然后重试
  • 超时:系统响应超时,请稍后重试
  • 502:
else if (message.includes('Request failed with status code')) {
  if (message.includes('502')) {
    message = `系统正在升级,请稍候。`
  } else {
    const code = message.substr(message.length - 3)
    message = `系统访问异常,请向客服提供错误代码:HTTP ${code}`
  }
}

后端报错向用户提供日志ID

GlobalExceptionHandler.java修改代码:

/**
 * 处理系统异常,兜底处理所有的一切
 */
@ExceptionHandler(value = Exception.class)
public CommonResult<?> defaultExceptionHandler(HttpServletRequest req, Throwable ex) {
    log.error("[defaultExceptionHandler]", ex);

    String message = INTERNAL_SERVER_ERROR.getMsg();

    // 插入异常日志
    // 如果能获取到事件id,则直接展示id,方便在系统中查询;如果获取不到id,则提供时间,方便去后台翻日志
    Long errId = this.createExceptionLog(req, ex);
    if (errId != null) {
        message += "(日志编号:#" + errId + ")";
    } else {
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("ddHHmmss");
        message += "(日志编号:$" + LocalDateTime.now().format(dtf) + ")";
    }

    // 返回 ERROR CommonResult
    return CommonResult.error(INTERNAL_SERVER_ERROR.getCode(), message);
}

private Long createExceptionLog(HttpServletRequest req, Throwable e) {
    // 插入错误日志
    ApiErrorLogCreateReqDTO errorLog = new ApiErrorLogCreateReqDTO();
    Long result = null;
    try {
        // 初始化 errorLog
        initExceptionLog(errorLog, req, e);
        // 执行插入 errorLog
        Future<Long> errId = apiErrorLogFrameworkService.createApiErrorLogAsync(errorLog);
        result = errId.get(3, TimeUnit.SECONDS);
    } catch (TimeoutException ex) {
        result = null;
    } catch (Throwable th) {
        log.error("[createExceptionLog][url({}) log({}) 发生异常]", req.getRequestURI(), JsonUtils.toJsonString(errorLog), th);
    }
    return result;
}

ApiErrorLogFrameworkService.java修改createApiErrorLogAsync返回类型:

/**
 * 创建 API 错误日志
 *
 * @param createDTO 创建信息
 * @return 日志ID
 */
Future<Long> createApiErrorLogAsync(@Valid ApiErrorLogCreateReqDTO createDTO);

ApiErrorLogServiceImpl.java修改:

@Override
@Async
public Future<Long> createApiErrorLogAsync(ApiErrorLogCreateReqDTO createDTO) {
    ApiErrorLogDO apiErrorLog = ApiErrorLogConvert.INSTANCE.convert(createDTO);
    apiErrorLog.setProcessStatus(ApiErrorLogProcessStatusEnum.INIT.getStatus());
    apiErrorLogMapper.insert(apiErrorLog);
    return new AsyncResult<>(apiErrorLog.getId());
}

完善日志查询,允许按ID查询日志

后端

ApiAccessLogPageReqVO.javaApiErrorLogPageReqVO.javaLoginLogPageReqVO.javaOperateLogPageReqVO.java增加:

@ApiModelProperty(value = "日志编号", example = "666")
private Long id;

ApiAccessLogMapper.javaApiErrorLogMapper.javaLoginLogMapper.javaOperateLogMapper.java增加:

selectPage()内加一行

.eqIfPresent("id", reqVO.getId())

前端

src/views/infra/apiAccessLog/index.vueapiErrorLog/index.vueoperatelog/index.vueloginlog/index.vue分别加入:

<el-form-item label="日志编号" prop="id">
  <el-input
    v-model="queryParams.id"
    placeholder="请输入日志编号"
    clearable
    size="small"
    @keyup.enter.native="handleQuery"
 />
</el-form-item>
...
queryParams: {
  ...
  id: null,
  ...
}

防止对话框意外关闭

前端全局搜索el-dialog,在每个el-dialog中加入

:close-on-click-modal="false"

修改后端的src/resources/codegen/vue/views/index.vue.vm,在el-dialog后面增加:

:close-on-click-modal="false"

这样再生成代码就不会遇到相同问题了。

处理前端列表页面,搜索条件文字宽度太窄问题

前端全局搜索label-width="68px",将数字改得大一些,例如75px。

修改后端的src/resources/codegen/vue/views/index.vue.vm,将68px换成相同的数字。

向前端提供username字段

修改后端AuthPermissionInfoRespVO.java

public static class UserVO {
    ...

    @ApiModelProperty(value = "用户名", required = true, example = "pdiwt源码")
    private String username;
}

修改AuthConvert.java,增加username

default AuthPermissionInfoRespVO convert(AdminUserDO user, List<RoleDO> roleList, List<MenuDO> menuList) {
    return AuthPermissionInfoRespVO.builder()
            .user(AuthPermissionInfoRespVO.UserVO.builder().id(user.getId()).nickname(user.getNickname()).avatar(user.getAvatar()).username(user.getUsername()).build())
        .roles(CollectionUtils.convertSet(roleList, RoleDO::getCode))
        .permissions(CollectionUtils.convertSet(menuList, MenuDO::getPermission))
        .build();
}

系统安全

log4j漏洞

定位pom.xml,将log4j版本直接改为最新版即可。

生产系统禁用Swagger

修改后端的application-prod.yaml,增加:

knife4j:
  enable: true
  production: true

如果是前后分离项目,也可以通过配置nginx等方式,将/doc.html地址屏蔽掉。

初始化

替换图像

前端src/assets/images,替换登录背景图片与默认头像。

删除admin头像:

update map.system_user set avatar='' where id=1;

改包名

在代码中改完包名之后,还需要执行一个sql(将下面的com.xxx换成实际包名),以免启动报错:

update map.pay_channel set config=replace(config,'cn.iocoder', 'com.xxx');

修改租户名

update map.system_tenant set name='app' where id=1;

在前端login.vue中将“租户”的输入框隐藏,并设置默认值。

前端nginx通用配置

upstream backend {
    # TODO 后端服务器地址
    server xxx:48080;
}

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {
    listen 80 default_server;
    listen [::]:80 default_server;

    gzip on;
    gzip_min_length 1k;
    gzip_comp_level 1;
    gzip_vary on;
    gzip_proxied any;
    gzip_disable "MSIE [1-6]\.";
    gzip_buffers 32 4k;
    gzip_http_version 1.1;
    gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php;

    # TODO 设置成前端地址
    root /var/www/html;

    # 上传大小限制
    client_max_body_size 500m;

    # 错误页,让403返回404(以应对扫测)
    error_page 403 =404 /404;
    error_page 404 /404;

    location = /404 {
        internal;
    }

    # 安全设置
    add_header X-Content-Type-Options nosniff;
    add_header X-XSS-Protection 1;

    # 管理后台
    location ^~ /api/ { try_files /dev/null @backend; }
    location ^~ /admin-api/ { try_files /dev/null @backend; }
    location ^~ /infra-api/ { try_files /dev/null @backend; }
    location @backend {
        proxy_pass http://backend;
        proxy_http_version 1.1;
        proxy_buffering off;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Host $http_host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_intercept_errors off;
    }

    # TODO 注意:以下是开发工具,应根据实际需要启停
    # Spring Boot监测
    # location /admin { try_files /dev/null @backend; }
    # location /druid { try_files /dev/null @backend; }

    # Swagger
    # location = /doc.html { try_files /dev/null @backend; }
    # location /webjars/ { try_files /dev/null @backend; }
    # location = /swagger-resources { try_files /dev/null @backend; }
    # location /v2/ { try_files /dev/null @backend; }

    # 前端
    location / {
        try_files $uri $uri/ /index.html;
    }
}