1、请求租户不存在时,记录访问日志、操作日志异常的问题
2、LoginUser 新增过期时间,方便判断 token 过期
3、增加 ConfigApi,支持参数配置的读取
已修改36个文件
已添加6个文件
507 ■■■■ 文件已修改
iailab-cloud/iailab-gateway/src/main/java/com/iailab/gateway/filter/security/LoginUser.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-cloud/iailab-gateway/src/main/java/com/iailab/gateway/filter/security/TokenAuthenticationFilter.java 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-cloud/iailab-gateway/src/main/resources/application-local.yaml 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-cloud/iailab-gateway/src/main/resources/application.yaml 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-cloud/iailab-nacos/src/main/resources/application.yaml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-cloud/iailab-xxl-job/src/main/resources/static/js/jobinfo.index.1.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-cloud/iailab-xxl-job/src/main/resources/templates/index.ftl 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-cloud/iailab-xxl-job/src/main/resources/templates/jobinfo/jobinfo.index.ftl 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-cloud/iailab-xxl-job/src/main/resources/templates/joblog/joblog.index.ftl 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-cloud/pom.xml 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-framework/iailab-common-biz-data-permission/pom.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-framework/iailab-common-biz-data-permission/src/main/java/com/iailab/framework/datapermission/config/IailabDataPermissionRpcAutoConfiguration.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-framework/iailab-common-biz-data-permission/src/main/java/com/iailab/framework/datapermission/core/rpc/DataPermissionRequestInterceptor.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-framework/iailab-common-biz-data-permission/src/main/java/com/iailab/framework/datapermission/core/rpc/DataPermissionRpcWebFilter.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-framework/iailab-common-biz-data-permission/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-framework/iailab-common-security/src/main/java/com/iailab/framework/security/core/LoginUser.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-framework/iailab-common-security/src/main/java/com/iailab/framework/security/core/filter/TokenAuthenticationFilter.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-framework/iailab-common-web/src/main/java/com/iailab/framework/apilog/core/service/ApiAccessLogFrameworkServiceImpl.java 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-framework/iailab-common-web/src/main/java/com/iailab/framework/apilog/core/service/ApiErrorLogFrameworkServiceImpl.java 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-framework/iailab-common-web/src/main/java/com/iailab/framework/jackson/config/IailabJacksonAutoConfiguration.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-framework/iailab-common-web/src/main/java/com/iailab/framework/web/core/handler/GlobalExceptionHandler.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-framework/iailab-common/src/main/java/com/iailab/framework/common/util/json/databind/NumberSerializer.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-framework/iailab-common/src/main/java/com/iailab/framework/common/util/json/databind/TimestampLocalDateTimeDeserializer.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-framework/iailab-common/src/main/java/com/iailab/framework/common/util/json/databind/TimestampLocalDateTimeSerializer.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/resources/application-local.yaml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-bpm/iailab-module-bpm-biz/src/main/resources/application.yaml 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-data/iailab-module-data-biz/src/main/java/com/iailab/module/data/job/task/PointCollectTaskNet10.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-infra/iailab-module-infra-api/src/main/java/com/iailab/module/infra/api/config/ConfigApi.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-infra/iailab-module-infra-biz/src/main/java/com/iailab/module/infra/api/config/ConfigApiImpl.java 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-infra/iailab-module-infra-biz/src/main/java/com/iailab/module/infra/service/logger/ApiAccessLogServiceImpl.java 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-infra/iailab-module-infra-biz/src/main/java/com/iailab/module/infra/service/logger/ApiErrorLogServiceImpl.java 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-infra/iailab-module-infra-biz/src/main/resources/application-local.yaml 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-infra/iailab-module-infra-biz/src/main/resources/application.yaml 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/job/task/DeviceHealthTask.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-api/src/main/java/com/iailab/module/system/api/oauth2/dto/OAuth2AccessTokenCheckRespDTO.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-api/src/main/java/com/iailab/module/system/enums/ErrorCodeConstants.java 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/api/oauth2/OAuth2TokenApiImpl.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/framework/rpc/config/RpcConfiguration.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/social/SocialClientServiceImpl.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/user/AdminUserServiceImpl.java 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/resources/application-local.yaml 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-module-system/iailab-module-system-biz/src/main/resources/application.yaml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
iailab-cloud/iailab-gateway/src/main/java/com/iailab/gateway/filter/security/LoginUser.java
@@ -2,6 +2,7 @@
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
@@ -35,5 +36,9 @@
     * 授权范围
     */
    private List<String> scopes;
    /**
     * 过期时间
     */
    private LocalDateTime expiresTime;
}
iailab-cloud/iailab-gateway/src/main/java/com/iailab/gateway/filter/security/TokenAuthenticationFilter.java
@@ -3,6 +3,7 @@
import cn.hutool.core.util.StrUtil;
import com.iailab.framework.common.core.KeyValue;
import com.iailab.framework.common.pojo.CommonResult;
import com.iailab.framework.common.util.date.LocalDateTimeUtils;
import com.iailab.framework.common.util.json.JsonUtils;
import com.iailab.gateway.util.SecurityFrameworkUtils;
import com.iailab.gateway.util.WebFrameworkUtils;
@@ -94,7 +95,8 @@
        // 重要说明:defaultIfEmpty 作用,保证 Mono.empty() 情况,可以继续执行 `flatMap 的 chain.filter(exchange)` 逻辑,避免返回给前端空的 Response!!
        return getLoginUser(exchange, token).defaultIfEmpty(LOGIN_USER_EMPTY).flatMap(user -> {
            // 1. 无用户,直接 filter 继续请求
            if (user == LOGIN_USER_EMPTY) {
            if (user == LOGIN_USER_EMPTY || // 下面 expiresTime 的判断,为了解决 token 实际已经过期的情况
                    user.getExpiresTime() == null || LocalDateTimeUtils.afterNow(user.getExpiresTime())) {
                return chain.filter(exchange);
            }
@@ -153,7 +155,8 @@
        OAuth2AccessTokenCheckRespDTO tokenInfo = result.getData();
        return new LoginUser().setId(tokenInfo.getUserId()).setUserType(tokenInfo.getUserType())
                .setInfo(tokenInfo.getUserInfo()) // 额外的用户信息
                .setTenantId(tokenInfo.getTenantId()).setScopes(tokenInfo.getScopes());
                .setTenantId(tokenInfo.getTenantId()).setScopes(tokenInfo.getScopes())
                .setExpiresTime(tokenInfo.getExpiresTime());
    }
    @Override
iailab-cloud/iailab-gateway/src/main/resources/application-local.yaml
@@ -10,4 +10,9 @@
        group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP
      config: # 【注册中心】配置项
        namespace: a7112341-c9e2-4177-bc5b-0d2e8cf0b3bb # 命名空间。这里使用 dev 开发环境
        group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP
        group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP
# 日志文件配置
logging:
  level:
    org.springframework.context.support.PostProcessorRegistrationDelegate: ERROR # TODO 芋艿:先禁用,Spring Boot 3.X 存在部分错误的 WARN 提示
iailab-cloud/iailab-gateway/src/main/resources/application.yaml
@@ -77,12 +77,14 @@
            - Path=/admin-api/statistics/**
          filters:
            - RewritePath=/admin-api/statistics/v3/api-docs, /v3/api-docs # 配置,保证转发到 /v3/api-docs
        - id: xxl-job-server
          uri: grayLb://iailab-job
        ## xxl-job
        - id: xxl-job-admin
          uri: grayLb://xxl-job-server
          predicates:
            - Path=/iailab-job/**
            - Path=/xxl-job-admin/**
        ## monitor
        - id: monitor-server
          uri: grayLb://iailab-monitor
          uri: grayLb://monitor-server
          predicates:
            - Path=/iailab-monitor/**
        ## data-server 服务
iailab-cloud/iailab-nacos/src/main/resources/application.yaml
@@ -1,7 +1,7 @@
db:
  num: 1
  url:
    '0': jdbc:mysql://172.16.8.100:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
    '0': jdbc:mysql://localhost:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
  password:
    '0': 123456
  user:
iailab-cloud/iailab-xxl-job/src/main/resources/static/js/jobinfo.index.1.js
@@ -10,7 +10,7 @@
            type:"post",
            data : function ( d ) {
                var obj = {};
                obj.jobGroup = $('#jobGroup').val();
                obj.jobGroup = Number($('#jobGroup').val());
                obj.triggerStatus = $('#triggerStatus').val();
                obj.jobDesc = $('#jobDesc').val();
                obj.executorHandler = $('#executorHandler').val();
iailab-cloud/iailab-xxl-job/src/main/resources/templates/index.ftl
@@ -41,7 +41,7 @@
                        <div class="info-box-content">
                            <span class="info-box-text">${I18n.job_dashboard_job_num}</span>
                            <span class="info-box-number">${jobInfoCount}</span>
                            <span class="info-box-number">${jobInfoCount?number?string(',##0')}</span>
                            <div class="progress">
                                <div class="progress-bar" style="width: 100%"></div>
@@ -58,7 +58,7 @@
                        <div class="info-box-content">
                            <span class="info-box-text">${I18n.job_dashboard_trigger_num}</span>
                            <span class="info-box-number">${jobLogCount}</span>
                            <span class="info-box-number">${jobLogCount?number?string(',##0')}</span>
                            <div class="progress">
                                <div class="progress-bar" style="width: 100%" ></div>
@@ -80,7 +80,7 @@
                        <div class="info-box-content">
                            <span class="info-box-text">${I18n.job_dashboard_jobgroup_num}</span>
                            <span class="info-box-number">${executorCount}</span>
                            <span class="info-box-number">${executorCount?number?string(',##0')}</span>
                            <div class="progress">
                                <div class="progress-bar" style="width: 100%"></div>
iailab-cloud/iailab-xxl-job/src/main/resources/templates/jobinfo/jobinfo.index.ftl
@@ -30,7 +30,7 @@
                        <span class="input-group-addon">${I18n.jobinfo_field_jobgroup}</span>
                        <select class="form-control" id="jobGroup" >
                            <#list JobGroupList as group>
                                <option value="${group.id}" <#if jobGroup==group.id>selected</#if> >${group.title}</option>
                                <option value="${group.id?number?string(',##0')}" <#if jobGroup==group.id>selected</#if> >${group.title}</option>
                            </#list>
                          </select>
                      </div>
@@ -121,7 +121,7 @@
                        <div class="col-sm-4">
                            <select class="form-control" name="jobGroup" >
                                <#list JobGroupList as group>
                                    <option value="${group.id}" <#if jobGroup==group.id>selected</#if> >${group.title}</option>
                                    <option value="${group.id?number?string(',##0')}" <#if jobGroup==group.id>selected</#if> >${group.title}</option>
                                </#list>
                              </select>
                        </div>
@@ -366,7 +366,7 @@
                        <div class="col-sm-4">
                            <select class="form-control" name="jobGroup" >
                                <#list JobGroupList as group>
                                    <option value="${group.id}" >${group.title}</option>
                                    <option value="${group.id?number?string(',##0')}" >${group.title}</option>
                                </#list>
                            </select>
                        </div>
iailab-cloud/iailab-xxl-job/src/main/resources/templates/joblog/joblog.index.ftl
@@ -34,7 +34,7 @@
                                <option value="0" >${I18n.system_all}</option>  <#-- 仅管理员支持查询全部;普通用户仅支持查询有权限的 jobGroup -->
                            </#if>
                            <#list JobGroupList as group>
                                <option value="${group.id}" >${group.title}</option>
                                <option value="${group.id?number?string(',##0')}" >${group.title}</option>
                            </#list>
                          </select>
                      </div>
iailab-cloud/pom.xml
@@ -11,19 +11,10 @@
    <modules>
        <module>iailab-gateway</module>
        <module>iailab-xxl-job</module>
        <module>iailab-monitor</module>
        <module>iailab-nacos</module>
    </modules>
    <artifactId>iailab-cloud</artifactId>
    <packaging>pom</packaging>
    <name>${project.artifactId}</name>
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
</project>
iailab-framework/iailab-common-biz-data-permission/pom.xml
@@ -34,6 +34,13 @@
            <artifactId>iailab-common-mybatis</artifactId>
        </dependency>
        <!-- RPC 远程调用相关 -->
        <dependency>
            <groupId>com.iailab</groupId>
            <artifactId>iailab-common-rpc</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- 业务组件 -->
        <dependency>
            <groupId>com.iailab</groupId>
iailab-framework/iailab-common-biz-data-permission/src/main/java/com/iailab/framework/datapermission/config/IailabDataPermissionRpcAutoConfiguration.java
对比新文件
@@ -0,0 +1,35 @@
package com.iailab.framework.datapermission.config;
import com.iailab.framework.datapermission.core.rpc.DataPermissionRequestInterceptor;
import com.iailab.framework.datapermission.core.rpc.DataPermissionRpcWebFilter;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import static com.iailab.framework.common.enums.WebFilterOrderEnum.TENANT_CONTEXT_FILTER;
/**
 * 数据权限针对 RPC 的自动配置类
 *
 * @author 芋道源码
 */
@AutoConfiguration
@ConditionalOnClass(name = "feign.RequestInterceptor")
public class IailabDataPermissionRpcAutoConfiguration {
    @Bean
    public DataPermissionRequestInterceptor dataPermissionRequestInterceptor() {
        return new DataPermissionRequestInterceptor();
    }
    @Bean
    public FilterRegistrationBean<DataPermissionRpcWebFilter> dataPermissionRpcFilter() {
        FilterRegistrationBean<DataPermissionRpcWebFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(new DataPermissionRpcWebFilter());
        registrationBean.setOrder(TENANT_CONTEXT_FILTER - 1); // 顺序没有绝对的要求,在租户 Filter 前面稳妥点
        return registrationBean;
    }
}
iailab-framework/iailab-common-biz-data-permission/src/main/java/com/iailab/framework/datapermission/core/rpc/DataPermissionRequestInterceptor.java
对比新文件
@@ -0,0 +1,27 @@
package com.iailab.framework.datapermission.core.rpc;
import com.iailab.framework.datapermission.core.annotation.DataPermission;
import com.iailab.framework.datapermission.core.aop.DataPermissionContextHolder;
import feign.RequestInterceptor;
import feign.RequestTemplate;
/**
 * DataPermission 的 RequestInterceptor 实现类:Feign 请求时,将 {@link DataPermission} 设置到 header 中,继续透传给被调用的服务
 *
 * 注意:由于 {@link DataPermission} 不支持序列化和反序列化,所以暂时只能传递它的 enable 属性
 *
 * @author 芋道源码
 */
public class DataPermissionRequestInterceptor implements RequestInterceptor {
    public static final String ENABLE_HEADER_NAME = "data-permission-enable";
    @Override
    public void apply(RequestTemplate requestTemplate) {
        DataPermission dataPermission = DataPermissionContextHolder.get();
        if (dataPermission != null && Boolean.FALSE.equals(dataPermission.enable())) {
            requestTemplate.header(ENABLE_HEADER_NAME, "false");
        }
    }
}
iailab-framework/iailab-common-biz-data-permission/src/main/java/com/iailab/framework/datapermission/core/rpc/DataPermissionRpcWebFilter.java
对比新文件
@@ -0,0 +1,37 @@
package com.iailab.framework.datapermission.core.rpc;
import com.iailab.framework.datapermission.core.util.DataPermissionUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Objects;
/**
 * 针对 {@link DataPermissionRequestInterceptor} 的 RPC 调用,设置 {@link com.iailab.framework.datapermission.core.aop.DataPermissionContextHolder} 的上下文
 *
 * @author 芋道源码
 */
public class DataPermissionRpcWebFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException {
        String enable = request.getHeader(DataPermissionRequestInterceptor.ENABLE_HEADER_NAME);
        if (Objects.equals(enable, Boolean.FALSE.toString())) {
            DataPermissionUtils.executeIgnore(() -> {
                try {
                    chain.doFilter(request, response);
                } catch (IOException | ServletException e) {
                    throw new RuntimeException(e);
                }
            });
        } else {
            chain.doFilter(request, response);
        }
    }
}
iailab-framework/iailab-common-biz-data-permission/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -1,2 +1,3 @@
com.iailab.framework.datapermission.config.IailabDataPermissionAutoConfiguration
com.iailab.framework.datapermission.config.IailabDeptDataPermissionAutoConfiguration
com.iailab.framework.datapermission.config.IailabDataPermissionRpcAutoConfiguration
iailab-framework/iailab-common-security/src/main/java/com/iailab/framework/security/core/LoginUser.java
@@ -5,6 +5,7 @@
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -42,6 +43,10 @@
     * 授权范围
     */
    private List<String> scopes;
    /**
     * 过期时间
     */
    private LocalDateTime expiresTime;
    // ========== 上下文 ==========
    /**
iailab-framework/iailab-common-security/src/main/java/com/iailab/framework/security/core/filter/TokenAuthenticationFilter.java
@@ -97,7 +97,8 @@
            // 构建登录用户
            return new LoginUser().setId(accessToken.getUserId()).setUserType(accessToken.getUserType())
                    .setInfo(accessToken.getUserInfo()) // 额外的用户信息
                    .setTenantId(accessToken.getTenantId()).setScopes(accessToken.getScopes());
                    .setTenantId(accessToken.getTenantId()).setScopes(accessToken.getScopes())
                    .setExpiresTime(accessToken.getExpiresTime());
        } catch (ServiceException serviceException) {
            // 校验 Token 不通过时,考虑到一些接口是无需登录的,所以直接返回 null 即可
            return null;
iailab-framework/iailab-common-web/src/main/java/com/iailab/framework/apilog/core/service/ApiAccessLogFrameworkServiceImpl.java
@@ -3,6 +3,7 @@
import com.iailab.module.infra.api.logger.ApiAccessLogApi;
import com.iailab.module.infra.api.logger.dto.ApiAccessLogCreateReqDTO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
/**
@@ -13,6 +14,7 @@
 * @author iailab
 */
@RequiredArgsConstructor
@Slf4j
public class ApiAccessLogFrameworkServiceImpl implements ApiAccessLogFrameworkService {
    private final ApiAccessLogApi apiAccessLogApi;
@@ -20,7 +22,12 @@
    @Override
    @Async
    public void createApiAccessLog(ApiAccessLogCreateReqDTO reqDTO) {
        apiAccessLogApi.createApiAccessLog(reqDTO);
        try {
            apiAccessLogApi.createApiAccessLog(reqDTO);
        } catch (Throwable ex) {
            // 由于 @Async 异步调用,这里打印下日志,更容易跟进
            log.error("[createApiAccessLog][url({}) log({}) 发生异常]", reqDTO.getRequestUrl(), reqDTO, ex);
        }
    }
}
iailab-framework/iailab-common-web/src/main/java/com/iailab/framework/apilog/core/service/ApiErrorLogFrameworkServiceImpl.java
@@ -3,6 +3,7 @@
import com.iailab.module.infra.api.logger.ApiErrorLogApi;
import com.iailab.module.infra.api.logger.dto.ApiErrorLogCreateReqDTO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
/**
@@ -13,6 +14,7 @@
 * @author iailab
 */
@RequiredArgsConstructor
@Slf4j
public class ApiErrorLogFrameworkServiceImpl implements ApiErrorLogFrameworkService {
    private final ApiErrorLogApi apiErrorLogApi;
@@ -20,7 +22,12 @@
    @Override
    @Async
    public void createApiErrorLog(ApiErrorLogCreateReqDTO reqDTO) {
        apiErrorLogApi.createApiErrorLog(reqDTO);
        try {
            apiErrorLogApi.createApiErrorLog(reqDTO);
        } catch (Throwable ex) {
            // 由于 @Async 异步调用,这里打印下日志,更容易跟进
            log.error("[createApiErrorLog][url({}) log({}) 发生异常]", reqDTO.getRequestUrl(), reqDTO, ex);
        }
    }
}
iailab-framework/iailab-common-web/src/main/java/com/iailab/framework/jackson/config/IailabJacksonAutoConfiguration.java
@@ -2,15 +2,15 @@
import cn.hutool.core.collection.CollUtil;
import com.iailab.framework.common.util.json.JsonUtils;
import com.iailab.framework.jackson.core.databind.NumberSerializer;
import com.iailab.framework.jackson.core.databind.TimestampLocalDateTimeDeserializer;
import com.iailab.framework.jackson.core.databind.TimestampLocalDateTimeSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import com.iailab.framework.common.util.json.databind.NumberSerializer;
import com.iailab.framework.common.util.json.databind.TimestampLocalDateTimeDeserializer;
import com.iailab.framework.common.util.json.databind.TimestampLocalDateTimeSerializer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.Bean;
iailab-framework/iailab-common-web/src/main/java/com/iailab/framework/web/core/handler/GlobalExceptionHandler.java
@@ -205,9 +205,11 @@
     */
    @ExceptionHandler(value = ServiceException.class)
    public CommonResult<?> serviceExceptionHandler(ServiceException ex) {
        // 不包含的时候,才进行打印,避免 ex 堆栈过多
        if (!IGNORE_ERROR_MESSAGES.contains(ex.getMessage())) {
            // 不包含的时候,才进行打印,避免 ex 堆栈过多
            log.info("[serviceExceptionHandler]", ex);
            // 即使打印,也只打印第一层 StackTraceElement,并且使用 warn 在控制台输出,更容易看到
            StackTraceElement[] stackTrace = ex.getStackTrace();
            log.warn("[serviceExceptionHandler]\n\t{}", stackTrace[0]);
        }
        return CommonResult.error(ex.getCode(), ex.getMessage());
    }
@@ -287,45 +289,51 @@
        }
        // 1. 数据报表
        if (message.contains("report_")) {
            log.error("[报表模块 iailab-module-report - 表结构未导入][参考 https://doc.iocoder.cn/report/ 开启]");
            log.error("[报表模块 yudao-module-report - 表结构未导入][参考 https://cloud.iocoder.cn/report/ 开启]");
            return CommonResult.error(NOT_IMPLEMENTED.getCode(),
                    "[报表模块 iailab-module-report - 表结构未导入][参考 https://doc.iocoder.cn/report/ 开启]");
                    "[报表模块 yudao-module-report - 表结构未导入][参考 https://cloud.iocoder.cn/report/ 开启]");
        }
        // 2. 工作流
        if (message.contains("bpm_")) {
            log.error("[工作流模块 iailab-module-bpm - 表结构未导入][参考 https://doc.iocoder.cn/bpm/ 开启]");
            log.error("[工作流模块 yudao-module-bpm - 表结构未导入][参考 https://cloud.iocoder.cn/bpm/ 开启]");
            return CommonResult.error(NOT_IMPLEMENTED.getCode(),
                    "[工作流模块 iailab-module-bpm - 表结构未导入][参考 https://doc.iocoder.cn/bpm/ 开启]");
                    "[工作流模块 yudao-module-bpm - 表结构未导入][参考 https://cloud.iocoder.cn/bpm/ 开启]");
        }
        // 3. 微信公众号
        if (message.contains("mp_")) {
            log.error("[微信公众号 iailab-module-mp - 表结构未导入][参考 https://doc.iocoder.cn/mp/build/ 开启]");
            log.error("[微信公众号 yudao-module-mp - 表结构未导入][参考 https://cloud.iocoder.cn/mp/build/ 开启]");
            return CommonResult.error(NOT_IMPLEMENTED.getCode(),
                    "[微信公众号 iailab-module-mp - 表结构未导入][参考 https://doc.iocoder.cn/mp/build/ 开启]");
                    "[微信公众号 yudao-module-mp - 表结构未导入][参考 https://cloud.iocoder.cn/mp/build/ 开启]");
        }
        // 4. 商城系统
        if (StrUtil.containsAny(message, "product_", "promotion_", "trade_")) {
            log.error("[商城系统 iailab-module-mall - 已禁用][参考 https://doc.iocoder.cn/mall/build/ 开启]");
            log.error("[商城系统 yudao-module-mall - 已禁用][参考 https://cloud.iocoder.cn/mall/build/ 开启]");
            return CommonResult.error(NOT_IMPLEMENTED.getCode(),
                    "[商城系统 iailab-module-mall - 已禁用][参考 https://doc.iocoder.cn/mall/build/ 开启]");
                    "[商城系统 yudao-module-mall - 已禁用][参考 https://cloud.iocoder.cn/mall/build/ 开启]");
        }
        // 5. ERP 系统
        if (message.contains("erp_")) {
            log.error("[ERP 系统 iailab-module-erp - 表结构未导入][参考 https://doc.iocoder.cn/erp/build/ 开启]");
            log.error("[ERP 系统 yudao-module-erp - 表结构未导入][参考 https://cloud.iocoder.cn/erp/build/ 开启]");
            return CommonResult.error(NOT_IMPLEMENTED.getCode(),
                    "[ERP 系统 iailab-module-erp - 表结构未导入][参考 https://doc.iocoder.cn/erp/build/ 开启]");
                    "[ERP 系统 yudao-module-erp - 表结构未导入][参考 https://cloud.iocoder.cn/erp/build/ 开启]");
        }
        // 6. CRM 系统
        if (message.contains("crm_")) {
            log.error("[CRM 系统 iailab-module-crm - 表结构未导入][参考 https://doc.iocoder.cn/crm/build/ 开启]");
            log.error("[CRM 系统 yudao-module-crm - 表结构未导入][参考 https://cloud.iocoder.cn/crm/build/ 开启]");
            return CommonResult.error(NOT_IMPLEMENTED.getCode(),
                    "[CRM 系统 iailab-module-crm - 表结构未导入][参考 https://doc.iocoder.cn/crm/build/ 开启]");
                    "[CRM 系统 yudao-module-crm - 表结构未导入][参考 https://cloud.iocoder.cn/crm/build/ 开启]");
        }
        // 7. 支付平台
        if (message.contains("pay_")) {
            log.error("[支付模块 iailab-module-pay - 表结构未导入][参考 https://doc.iocoder.cn/pay/build/ 开启]");
            log.error("[支付模块 yudao-module-pay - 表结构未导入][参考 https://cloud.iocoder.cn/pay/build/ 开启]");
            return CommonResult.error(NOT_IMPLEMENTED.getCode(),
                    "[支付模块 iailab-module-pay - 表结构未导入][参考 https://doc.iocoder.cn/pay/build/ 开启]");
                    "[支付模块 yudao-module-pay - 表结构未导入][参考 https://cloud.iocoder.cn/pay/build/ 开启]");
        }
        // 8. AI 大模型
        if (message.contains("ai_")) {
            log.error("[AI 大模型 yudao-module-ai - 表结构未导入][参考 https://cloud.iocoder.cn/ai/build/ 开启]");
            return CommonResult.error(NOT_IMPLEMENTED.getCode(),
                    "[AI 大模型 yudao-module-ai - 表结构未导入][参考 https://cloud.iocoder.cn/ai/build/ 开启]");
        }
        return null;
    }
iailab-framework/iailab-common/src/main/java/com/iailab/framework/common/util/json/databind/NumberSerializer.java
对比新文件
@@ -0,0 +1,37 @@
package com.iailab.framework.common.util.json.databind;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
import java.io.IOException;
/**
 * Long 序列化规则
 *
 * 会将超长 long 值转换为 string,解决前端 JavaScript 最大安全整数是 2^53-1 的问题
 *
 * @author 星语
 */
@JacksonStdImpl
public class NumberSerializer extends com.fasterxml.jackson.databind.ser.std.NumberSerializer {
    private static final long MAX_SAFE_INTEGER = 9007199254740991L;
    private static final long MIN_SAFE_INTEGER = -9007199254740991L;
    public static final NumberSerializer INSTANCE = new NumberSerializer(Number.class);
    public NumberSerializer(Class<? extends Number> rawType) {
        super(rawType);
    }
    @Override
    public void serialize(Number value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        // 超出范围 序列化位字符串
        if (value.longValue() > MIN_SAFE_INTEGER && value.longValue() < MAX_SAFE_INTEGER) {
            super.serialize(value, gen, serializers);
        } else {
            gen.writeString(value.toString());
        }
    }
}
iailab-framework/iailab-common/src/main/java/com/iailab/framework/common/util/json/databind/TimestampLocalDateTimeDeserializer.java
对比新文件
@@ -0,0 +1,27 @@
package com.iailab.framework.common.util.json.databind;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import java.io.IOException;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
/**
 * 基于时间戳的 LocalDateTime 反序列化器
 *
 * @author 老五
 */
public class TimestampLocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
    public static final TimestampLocalDateTimeDeserializer INSTANCE = new TimestampLocalDateTimeDeserializer();
    @Override
    public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        // 将 Long 时间戳,转换为 LocalDateTime 对象
        return LocalDateTime.ofInstant(Instant.ofEpochMilli(p.getValueAsLong()), ZoneId.systemDefault());
    }
}
iailab-framework/iailab-common/src/main/java/com/iailab/framework/common/util/json/databind/TimestampLocalDateTimeSerializer.java
对比新文件
@@ -0,0 +1,26 @@
package com.iailab.framework.common.util.json.databind;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.ZoneId;
/**
 * 基于时间戳的 LocalDateTime 序列化器
 *
 * @author 老五
 */
public class TimestampLocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {
    public static final TimestampLocalDateTimeSerializer INSTANCE = new TimestampLocalDateTimeSerializer();
    @Override
    public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        // 将 LocalDateTime 对象,转换为 Long 时间戳
        gen.writeNumber(value.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
    }
}
iailab-module-bpm/iailab-module-bpm-biz/src/main/resources/application-local.yaml
@@ -113,15 +113,8 @@
iailab:
  env: # 多环境的配置项
    tag: ${HOSTNAME}
  captcha:
    enable: false # 本地环境,暂时关闭图片验证码,方便登录等接口的测试
  security:
    mock-enable: true
  xss:
    enable: false
    exclude-urls: # 如下两个 url,仅仅是为了演示,去掉配置也没关系
      - ${spring.boot.admin.context-path}/** # 不处理 Spring Boot Admin 的请求
      - ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求
  access-log: # 访问日志的配置项
    enable: false
  demo: false # 关闭演示模式
iailab-module-bpm/iailab-module-bpm-biz/src/main/resources/application.yaml
@@ -108,15 +108,14 @@
  web:
    admin-ui:
      url: http://dashboard.iailab.iocoder.cn # Admin 管理后台 UI 的地址
  xss:
    enable: false
    exclude-urls: # 如下 url,仅仅是为了演示,去掉配置也没关系
      - ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求
  swagger:
    title: 管理后台
    description: 提供管理员管理的所有功能
    version: ${iailab.info.version}
    base-package: ${iailab.info.base-package}
  captcha:
    timeout: 5m
    width: 160
    height: 60
  tenant: # 多租户相关配置项
    enable: true
iailab-module-data/iailab-module-data-biz/src/main/java/com/iailab/module/data/job/task/PointCollectTaskNet10.java
@@ -1,6 +1,7 @@
package com.iailab.module.data.job.task;
import com.iailab.module.data.point.collection.PointCollector;
import com.xxl.job.core.handler.annotation.XxlJob;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Resource;
@@ -27,6 +28,7 @@
    private PointCollector pointCollector;
    @Override
    @XxlJob("pointCollectTaskNet10")
    public void run(String params){
        // 0/10 * * * * ?
        logger.debug("PointCollectTaskNet10定时任务正在执行,参数为:{}", params);
iailab-module-infra/iailab-module-infra-api/src/main/java/com/iailab/module/infra/api/config/ConfigApi.java
@@ -1,17 +1,20 @@
package com.iailab.module.infra.api.config;
import com.iailab.framework.common.pojo.CommonResult;
import com.iailab.module.infra.enums.ApiConstants;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
@FeignClient(name = ApiConstants.NAME)
@Tag(name = "配置文件")
@FeignClient(name = ApiConstants.NAME) // TODO 芋艿:fallbackFactory =
@Tag(name = "RPC 服务 - 参数配置")
public interface ConfigApi {
    @Operation(summary = "Feign接口-查询配置参数")
    @GetMapping("/api/feign/infra-config/{configCode}")
    String queryConfigByCode(@PathVariable("configCode") String configCode);
    String PREFIX = ApiConstants.PREFIX + "/config";
    @GetMapping(PREFIX + "/get-value-by-key")
    @Operation(summary = "根据参数键查询参数值")
    CommonResult<String> getConfigValueByKey(@RequestParam("key") String key);
}
iailab-module-infra/iailab-module-infra-biz/src/main/java/com/iailab/module/infra/api/config/ConfigApiImpl.java
@@ -1,18 +1,12 @@
package com.iailab.module.infra.api.config;
import com.iailab.framework.common.pojo.CommonResult;
import com.iailab.module.infra.api.file.FileApi;
import com.iailab.module.infra.api.file.dto.FileCreateReqDTO;
import com.iailab.module.infra.dal.dataobject.config.ConfigDO;
import com.iailab.module.infra.service.config.ConfigService;
import com.iailab.module.infra.service.file.FileService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import static com.iailab.framework.common.pojo.CommonResult.success;
@@ -23,49 +17,11 @@
    @Resource
    private ConfigService configService;
//    /**
//     * 查月初或者年初(根据code)
//     *
//     * @return
//     */
//    @Override
//    public String queryConfigByCode(String configCode, String type){
//        String result = "";
//        String value = configService.getValue(configCode);
//        Calendar calendar = Calendar.getInstance();
//        calendar.setTime(new Date());
//        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
//        if("month".equals(type)){
//            int nowDay = calendar.get(Calendar.DAY_OF_MONTH);
//            if(nowDay < Integer.parseInt(value)){
//                calendar.add(Calendar.MONTH, -1);
//            }
//            calendar.set(Calendar.DAY_OF_MONTH, Integer.parseInt(value));
//        } else {
//            int month = Integer.parseInt(value.substring(0,value.indexOf("-")));
//            int day = Integer.parseInt(value.substring(value.indexOf("-") + 1));
//            int nowMonth = calendar.get(Calendar.MONTH) + 1;
//            int nowDay = calendar.get(Calendar.DAY_OF_MONTH);
//
//            if(nowMonth == month) {
//                if(nowDay < day){
//                    calendar.add(Calendar.YEAR, -1);
//                }
//            } else if(nowMonth < month) {
//                calendar.add(Calendar.YEAR, -1);
//            }
//            calendar.set(Calendar.MONTH, month - 1);
//            calendar.set(Calendar.DAY_OF_MONTH, day);
//        }
//
//        result = sdf.format(calendar.getTime());
//
//        return result;
//    }
    @Override
    public String queryConfigByCode(String configCode) {
        return configService.getValue(configCode);
    public CommonResult<String> getConfigValueByKey(String key) {
        ConfigDO config = configService.getConfigByKey(key);
        return success(config != null ? config.getValue() : null);
    }
}
iailab-module-infra/iailab-module-infra-biz/src/main/java/com/iailab/module/infra/service/logger/ApiAccessLogServiceImpl.java
@@ -3,6 +3,8 @@
import cn.hutool.core.util.StrUtil;
import com.iailab.framework.common.pojo.PageResult;
import com.iailab.framework.common.util.object.BeanUtils;
import com.iailab.framework.tenant.core.context.TenantContextHolder;
import com.iailab.framework.tenant.core.util.TenantUtils;
import com.iailab.module.infra.api.logger.dto.ApiAccessLogCreateReqDTO;
import com.iailab.module.infra.controller.admin.logger.vo.apiaccesslog.ApiAccessLogPageReqVO;
import com.iailab.module.infra.dal.dataobject.logger.ApiAccessLogDO;
@@ -35,7 +37,12 @@
        ApiAccessLogDO apiAccessLog = BeanUtils.toBean(createDTO, ApiAccessLogDO.class);
        apiAccessLog.setRequestParams(StrUtil.maxLength(apiAccessLog.getRequestParams(), REQUEST_PARAMS_MAX_LENGTH));
        apiAccessLog.setResultMsg(StrUtil.maxLength(apiAccessLog.getResultMsg(), RESULT_MSG_MAX_LENGTH));
        apiAccessLogMapper.insert(apiAccessLog);
        if (TenantContextHolder.getTenantId() != null) {
            apiAccessLogMapper.insert(apiAccessLog);
        } else {
            // 极端情况下,上下文中没有租户时,此时忽略租户上下文,避免插入失败!
            TenantUtils.executeIgnore(() -> apiAccessLogMapper.insert(apiAccessLog));
        }
    }
    @Override
iailab-module-infra/iailab-module-infra-biz/src/main/java/com/iailab/module/infra/service/logger/ApiErrorLogServiceImpl.java
@@ -3,6 +3,8 @@
import cn.hutool.core.util.StrUtil;
import com.iailab.framework.common.pojo.PageResult;
import com.iailab.framework.common.util.object.BeanUtils;
import com.iailab.framework.tenant.core.context.TenantContextHolder;
import com.iailab.framework.tenant.core.util.TenantUtils;
import com.iailab.module.infra.api.logger.dto.ApiErrorLogCreateReqDTO;
import com.iailab.module.infra.controller.admin.logger.vo.apierrorlog.ApiErrorLogPageReqVO;
import com.iailab.module.infra.dal.dataobject.logger.ApiErrorLogDO;
@@ -25,9 +27,9 @@
 *
 * @author iailab
 */
@Slf4j
@Service
@Validated
@Slf4j
public class ApiErrorLogServiceImpl implements ApiErrorLogService {
    @Resource
@@ -38,7 +40,12 @@
        ApiErrorLogDO apiErrorLog = BeanUtils.toBean(createDTO, ApiErrorLogDO.class)
                .setProcessStatus(ApiErrorLogProcessStatusEnum.INIT.getStatus());
        apiErrorLog.setRequestParams(StrUtil.maxLength(apiErrorLog.getRequestParams(), REQUEST_PARAMS_MAX_LENGTH));
        apiErrorLogMapper.insert(apiErrorLog);
        if (TenantContextHolder.getTenantId() != null) {
            apiErrorLogMapper.insert(apiErrorLog);
        } else {
            // 极端情况下,上下文中没有租户时,此时忽略租户上下文,避免插入失败!
            TenantUtils.executeIgnore(() -> apiErrorLogMapper.insert(apiErrorLog));
        }
    }
    @Override
iailab-module-infra/iailab-module-infra-biz/src/main/resources/application-local.yaml
@@ -22,10 +22,10 @@
  autoconfigure:
    exclude:
      - com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure # 排除 Druid 的自动配置,使用 dynamic-datasource-spring-boot-starter 配置多数据源
      - de.codecentric.boot.admin.server.config.AdminServerAutoConfiguration # 禁用 Spring Boot Admin 的 Server 的自动配置
      - de.codecentric.boot.admin.server.cloud.config.AdminServerDiscoveryAutoConfiguration # 禁用 Spring Boot Admin 的 Server 的自动配置
      - de.codecentric.boot.admin.server.ui.config.AdminServerUiAutoConfiguration # 禁用 Spring Boot Admin 的 Server UI 的自动配置
      - de.codecentric.boot.admin.client.config.SpringBootAdminClientAutoConfiguration # 禁用 Spring Boot Admin 的 Client 的自动配置
#      - de.codecentric.boot.admin.server.config.AdminServerAutoConfiguration # 禁用 Spring Boot Admin 的 Server 的自动配置
#      - de.codecentric.boot.admin.server.cloud.config.AdminServerDiscoveryAutoConfiguration # 禁用 Spring Boot Admin 的 Server 的自动配置
#      - de.codecentric.boot.admin.server.ui.config.AdminServerUiAutoConfiguration # 禁用 Spring Boot Admin 的 Server UI 的自动配置
#      - de.codecentric.boot.admin.client.config.SpringBootAdminClientAutoConfiguration # 禁用 Spring Boot Admin 的 Client 的自动配置
  datasource:
    druid: # Druid 【监控】相关的全局配置
      web-stat-filter:
@@ -154,11 +154,5 @@
    tag: ${HOSTNAME}
  security:
    mock-enable: true
  xss:
    enable: false
    exclude-urls: # 如下两个 url,仅仅是为了演示,去掉配置也没关系
      - ${spring.boot.admin.context-path}/** # 不处理 Spring Boot Admin 的请求
      - ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求
  access-log: # 访问日志的配置项
    enable: false
  demo: false # 关闭演示模式
iailab-module-infra/iailab-module-infra-biz/src/main/resources/application.yaml
@@ -138,6 +138,11 @@
  web:
    admin-ui:
      url: http://dashboard.iailab.iocoder.cn # Admin 管理后台 UI 的地址
  xss:
    enable: false
    exclude-urls: # 如下两个 url,仅仅是为了演示,去掉配置也没关系
      - ${spring.boot.admin.context-path}/** # 不处理 Spring Boot Admin 的请求
      - ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求
  websocket:
    enable: true # websocket的开关
    path: /infra/ws # 路径
@@ -155,7 +160,6 @@
    title: 管理后台
    description: 提供管理员管理的所有功能
    version: ${iailab.info.version}
    base-package: ${iailab.info.base-package}
  codegen:
    base-package: com.iailab
    db-schemas: ${spring.datasource.dynamic.datasource.master.name}
iailab-module-model/iailab-module-model-biz/src/main/java/com/iailab/module/job/task/DeviceHealthTask.java
@@ -15,6 +15,7 @@
import com.iailab.module.model.sample.constructor.SampleInfoConstructor;
import com.iailab.module.model.sample.dto.ColumnItem;
import com.iailab.module.model.sample.entity.DataEntity;
import com.xxl.job.core.handler.annotation.XxlJob;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -97,6 +98,7 @@
    }
    @Override
    @XxlJob("deviceHealthTask")
    public void run(String params) {
        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.MILLISECOND, 0);
iailab-module-system/iailab-module-system-api/src/main/java/com/iailab/module/system/api/oauth2/dto/OAuth2AccessTokenCheckRespDTO.java
@@ -4,6 +4,7 @@
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
@@ -26,4 +27,8 @@
    @Schema(description = "授权范围的数组", example = "user_info")
    private List<String> scopes;
    @Schema(description = "过期时间", requiredMode = Schema.RequiredMode.REQUIRED)
    private LocalDateTime expiresTime;
}
iailab-module-system/iailab-module-system-api/src/main/java/com/iailab/module/system/enums/ErrorCodeConstants.java
@@ -14,7 +14,6 @@
    ErrorCode AUTH_LOGIN_USER_DISABLED = new ErrorCode(1_002_000_001, "登录失败,账号被禁用");
    ErrorCode AUTH_LOGIN_CAPTCHA_CODE_ERROR = new ErrorCode(1_002_000_004, "验证码不正确,原因:{}");
    ErrorCode AUTH_THIRD_LOGIN_NOT_BIND = new ErrorCode(1_002_000_005, "未绑定账号,需要进行绑定");
    ErrorCode AUTH_TOKEN_EXPIRED = new ErrorCode(1_002_000_006, "Token 已经过期");
    ErrorCode AUTH_MOBILE_NOT_EXISTS = new ErrorCode(1_002_000_007, "手机号不存在");
    // ========== 菜单模块 1-002-001-000 ==========
@@ -42,6 +41,7 @@
    ErrorCode USER_PASSWORD_FAILED = new ErrorCode(1_002_003_005, "用户密码校验失败");
    ErrorCode USER_IS_DISABLE = new ErrorCode(1_002_003_006, "名字为【{}】的用户已被禁用");
    ErrorCode USER_COUNT_MAX = new ErrorCode(1_002_003_008, "创建用户失败,原因:超过租户最大租户配额({})!");
    ErrorCode USER_IMPORT_INIT_PASSWORD = new ErrorCode(1_002_003_009, "初始密码不能为空");
    // ========== 部门模块 1-002-004-000 ==========
    ErrorCode DEPT_NAME_DUPLICATE = new ErrorCode(1_002_004_000, "已经存在该名字的部门");
@@ -49,7 +49,6 @@
    ErrorCode DEPT_NOT_FOUND = new ErrorCode(1_002_004_002, "当前部门不存在");
    ErrorCode DEPT_EXITS_CHILDREN = new ErrorCode(1_002_004_003, "存在子部门,无法删除");
    ErrorCode DEPT_PARENT_ERROR = new ErrorCode(1_002_004_004, "不能设置自己为父部门");
    ErrorCode DEPT_EXISTS_USER = new ErrorCode(1_002_004_005, "部门中存在员工,无法删除");
    ErrorCode DEPT_NOT_ENABLE = new ErrorCode(1_002_004_006, "部门({})不处于开启状态,不允许选择");
    ErrorCode DEPT_PARENT_IS_CHILD = new ErrorCode(1_002_004_007, "不能设置自己的子部门为父部门");
@@ -96,11 +95,8 @@
    ErrorCode SMS_CODE_NOT_FOUND = new ErrorCode(1_002_014_000, "验证码不存在");
    ErrorCode SMS_CODE_EXPIRED = new ErrorCode(1_002_014_001, "验证码已过期");
    ErrorCode SMS_CODE_USED = new ErrorCode(1_002_014_002, "验证码已使用");
    ErrorCode SMS_CODE_NOT_CORRECT = new ErrorCode(1_002_014_003, "验证码不正确");
    ErrorCode SMS_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY = new ErrorCode(1_002_014_004, "超过每日短信发送数量");
    ErrorCode SMS_CODE_SEND_TOO_FAST = new ErrorCode(1_002_014_005, "短信发送过于频繁");
    ErrorCode SMS_CODE_IS_EXISTS = new ErrorCode(1_002_014_006, "手机号已被使用");
    ErrorCode SMS_CODE_IS_UNUSED = new ErrorCode(1_002_014_007, "验证码未被使用");
    // ========== 租户信息 1-002-015-000 ==========
    ErrorCode TENANT_NOT_EXISTS = new ErrorCode(1_002_015_000, "租户不存在");
@@ -136,7 +132,6 @@
    ErrorCode OAUTH2_GRANT_CLIENT_ID_MISMATCH = new ErrorCode(1_002_021_000, "client_id 不匹配");
    ErrorCode OAUTH2_GRANT_REDIRECT_URI_MISMATCH = new ErrorCode(1_002_021_001, "redirect_uri 不匹配");
    ErrorCode OAUTH2_GRANT_STATE_MISMATCH = new ErrorCode(1_002_021_002, "state 不匹配");
    ErrorCode OAUTH2_GRANT_CODE_NOT_EXISTS = new ErrorCode(1_002_021_003, "code 不存在");
    // ========== OAuth2 授权 1-002-022-000 =========
    ErrorCode OAUTH2_CODE_NOT_EXISTS = new ErrorCode(1_002_022_000, "code 不存在");
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/api/oauth2/OAuth2TokenApiImpl.java
@@ -23,7 +23,6 @@
    private OAuth2TokenService oauth2TokenService;
    @Override
    @Operation(description = "创建访问令牌")
    public CommonResult<OAuth2AccessTokenRespDTO> createAccessToken(OAuth2AccessTokenCreateReqDTO reqDTO) {
        OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.createAccessToken(
                reqDTO.getUserId(), reqDTO.getUserType(), reqDTO.getClientId(), reqDTO.getScopes());
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/framework/rpc/config/RpcConfiguration.java
@@ -1,11 +1,12 @@
package com.iailab.module.system.framework.rpc.config;
import com.iailab.module.infra.api.config.ConfigApi;
import com.iailab.module.infra.api.file.FileApi;
import com.iailab.module.infra.api.websocket.WebSocketSenderApi;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
@EnableFeignClients(clients = {FileApi.class, WebSocketSenderApi.class})
@EnableFeignClients(clients = {FileApi.class, WebSocketSenderApi.class, ConfigApi.class})
public class RpcConfiguration {
}
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/social/SocialClientServiceImpl.java
@@ -38,6 +38,7 @@
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
import me.chanjar.weixin.mp.config.impl.WxMpRedisConfigImpl;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
@@ -59,6 +60,16 @@
@Slf4j
public class SocialClientServiceImpl implements SocialClientService {
    /**
     * 小程序版本
     *
     * 1. release:正式版
     * 2. trial:体验版
     * 3. developer:开发版
     */
    @Value("${yudao.wxa-code.env-version:release}")
    public String envVersion;
    @Resource
    private AuthRequestFactory authRequestFactory;
iailab-module-system/iailab-module-system-biz/src/main/java/com/iailab/module/system/service/user/AdminUserServiceImpl.java
@@ -10,6 +10,7 @@
import com.iailab.framework.common.util.collection.CollectionUtils;
import com.iailab.framework.common.util.object.BeanUtils;
import com.iailab.framework.datapermission.core.util.DataPermissionUtils;
import com.iailab.module.infra.api.config.ConfigApi;
import com.iailab.module.infra.api.file.FileApi;
import com.iailab.module.system.controller.admin.user.vo.profile.UserProfileUpdatePasswordReqVO;
import com.iailab.module.system.controller.admin.user.vo.profile.UserProfileUpdateReqVO;
@@ -57,8 +58,7 @@
@Slf4j
public class AdminUserServiceImpl implements AdminUserService {
    @Value("${sys.user.init-password:iailabyuanma}")
    private String userInitPassword;
    static final String USER_INIT_PASSWORD_KEY = "system.user.init-password";
    @Resource
    private AdminUserMapper userMapper;
@@ -80,6 +80,9 @@
    @Resource
    private FileApi fileApi;
    @Resource
    private ConfigApi configApi;
    @Override
    @Transactional(rollbackFor = Exception.class)
@@ -428,9 +431,17 @@
    @Override
    @Transactional(rollbackFor = Exception.class) // 添加事务,异常则回滚所有导入
    public UserImportRespVO importUserList(List<UserImportExcelVO> importUsers, boolean isUpdateSupport) {
        // 1.1 参数校验
        if (CollUtil.isEmpty(importUsers)) {
            throw exception(USER_IMPORT_LIST_IS_EMPTY);
        }
        // 1.2 初始化密码不能为空
        String initPassword = configApi.getConfigValueByKey(USER_INIT_PASSWORD_KEY).getCheckedData();
        if (StrUtil.isEmpty(initPassword)) {
            throw exception(USER_IMPORT_INIT_PASSWORD);
        }
        // 2. 遍历,逐个创建 or 更新
        UserImportRespVO respVO = UserImportRespVO.builder().createUsernames(new ArrayList<>())
                .updateUsernames(new ArrayList<>()).failureUsernames(new LinkedHashMap<>()).build();
        importUsers.forEach(importUser -> {
iailab-module-system/iailab-module-system-biz/src/main/resources/application-local.yaml
@@ -173,14 +173,8 @@
    enable: false # 本地环境,暂时关闭图片验证码,方便登录等接口的测试
  security:
    mock-enable: true
  xss:
    enable: false
    exclude-urls: # 如下两个 url,仅仅是为了演示,去掉配置也没关系
      - ${spring.boot.admin.context-path}/** # 不处理 Spring Boot Admin 的请求
      - ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求
  access-log: # 访问日志的配置项
    enable: false
  demo: false # 关闭演示模式
justauth:
  enabled: true
iailab-module-system/iailab-module-system-biz/src/main/resources/application.yaml
@@ -158,13 +158,14 @@
  web:
    admin-ui:
      url: http://dashboard.iailab.iocoder.cn # Admin 管理后台 UI 的地址
  xss:
    enable: false
    exclude-urls: # 如下 url,仅仅是为了演示,去掉配置也没关系
      - ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求
  swagger:
    title: 管理后台
    description: 提供管理员管理的所有功能
    version: ${iailab.info.version}
    base-package: ${iailab.info.base-package}
  captcha:
    enable: false # 验证码的开关,默认为 true;
  tenant: # 多租户相关配置项
    enable: true
    ignore-urls: