baoshiwei
2025-04-19 9d960ed0058f9087f49e9741a9af06c3f9116eb0
feat(auth): 添加 Keycloak 认证支持

- 新增 Keycloak 认证请求类和配置类
- 实现 Keycloak 用户登录和绑定逻辑
- 更新登录服务以支持第三方认证
- 优化登出处理,支持 Keycloak会话结束
已添加16个文件
已修改24个文件
1742 ■■■■■ 文件已修改
zhitan-admin/pom.xml 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-admin/src/main/java/com/zhitan/web/controller/model/EnergyIndexController.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-admin/src/main/java/com/zhitan/web/controller/system/SysLoginController.java 35 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-admin/src/main/resources/application-dev.yml 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-admin/src/main/resources/application.yml 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-common/pom.xml 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-common/src/main/java/com/zhitan/common/config/keycloak/AuthKeycloakRequest.java 118 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-common/src/main/java/com/zhitan/common/config/keycloak/AuthKeycloakSource.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-common/src/main/java/com/zhitan/common/config/keycloak/AuthRedisStateCache.java 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-common/src/main/java/com/zhitan/common/config/keycloak/SocialLoginConfigProperties.java 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-common/src/main/java/com/zhitan/common/core/domain/model/LoginBody.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-common/src/main/java/com/zhitan/common/utils/JsonUtils.java 170 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-common/src/main/java/com/zhitan/common/utils/MapstructUtils.java 94 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-common/src/main/java/com/zhitan/common/utils/SocialUtils.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-common/src/main/java/com/zhitan/common/utils/SpringUtils.java 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-common/src/main/java/com/zhitan/common/utils/spring/SpringUtils.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-framework/pom.xml 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-framework/src/main/java/com/zhitan/framework/config/SecurityConfig.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-framework/src/main/java/com/zhitan/framework/security/handle/LogoutSuccessHandlerImpl.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-framework/src/main/java/com/zhitan/framework/web/service/SysLoginService.java 77 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-system/src/main/java/com/zhitan/model/mapper/EnergyIndexMapper.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-system/src/main/java/com/zhitan/model/service/IEnergyIndexService.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-system/src/main/java/com/zhitan/model/service/impl/EnergyIndexServiceImpl.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-system/src/main/java/com/zhitan/system/domain/SysSocial.java 133 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-system/src/main/java/com/zhitan/system/domain/bo/SysSocialBo.java 133 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-system/src/main/java/com/zhitan/system/domain/vo/SysSocialVo.java 139 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-system/src/main/java/com/zhitan/system/mapper/SysSocialMapper.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-system/src/main/java/com/zhitan/system/service/ISysSocialService.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-system/src/main/java/com/zhitan/system/service/impl/SysSocialServiceImpl.java 82 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-system/src/main/resources/mapper/model/EnergyIndexMapper.xml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-vue/.env.development 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-vue/src/api/login.js 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-vue/src/api/types.ts 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-vue/src/layout/components/SocialCallback/index.vue 97 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-vue/src/permission.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-vue/src/router/index.js 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-vue/src/utils/request.js 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-vue/src/views/login.vue 36 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-vue/src/views/modelconfiguration/collectindicator/collectIndicator.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-vue/vite.config.js 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zhitan-admin/pom.xml
@@ -65,6 +65,12 @@
            <scope>system</scope>
            <systemPath>${basedir}/lib/fel.jar</systemPath>
        </dependency>
        <dependency>
            <groupId>me.zhyd.oauth</groupId>
            <artifactId>JustAuth</artifactId>
            <version>1.16.7</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>
    <build>
zhitan-admin/src/main/java/com/zhitan/web/controller/model/EnergyIndexController.java
@@ -134,10 +134,17 @@
        .filter(f -> StringUtils.isBlank(f.getMeterId()))
        .map(EnergyIndex::getIndexId)
        .collect(Collectors.toList());
    /**
     * å¤„理能源指标列表并生成需要移除的指标ID集合
     * 1. è¿‡æ»¤å‡ºmeterId非空的能源指标对象
     * 2. æå–这些对象的indexId字段
     * 3. å°†æå–的指标ID收集到字符串集合中
     */
    List<String> removeLink = energyIndexList.stream()
        .filter(f -> StringUtils.isNotBlank(f.getMeterId()))
        .map(EnergyIndex::getIndexId)
        .collect(Collectors.toList());
    if (!removeLink.isEmpty()) {
      energyIndexService.removeNodeIndex(nodeId, removeLink);
    }
@@ -148,6 +155,16 @@
    return AjaxResult.success();
  }
  /**
   * æ–°å¢žé€šè¿‡id删除采集点接口
   */
  @PreAuthorize("@ss.hasPermi('energyindex:energyindex:remove')")
  @Log(title = "指标信息", businessType = BusinessType.DELETE)
  @DeleteMapping("/{indexId}")
  public AjaxResult deleteCollectIndex(@PathVariable String indexId) {
    return toAjax(energyIndexService.deleteByIndexId(indexId));
  }
  @Log(title = "增加计量器具采集点", businessType = BusinessType.INSERT)
  @PostMapping("/meterIndex/{meterId}")
  public AjaxResult addCollectIndex(@PathVariable("meterId") String meterId) {
zhitan-admin/src/main/java/com/zhitan/web/controller/system/SysLoginController.java
@@ -1,12 +1,18 @@
package com.zhitan.web.controller.system;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.ObjectUtil;
import com.zhitan.common.utils.SocialUtils;
import me.zhyd.oauth.request.AuthRequest;
import me.zhyd.oauth.utils.AuthStateUtils;
import org.springframework.web.bind.annotation.*;
import com.zhitan.common.constant.Constants;
import com.zhitan.common.core.domain.AjaxResult;
import com.zhitan.common.core.domain.entity.SysMenu;
@@ -45,11 +51,17 @@
    {
        AjaxResult ajax = AjaxResult.success();
        // ç”Ÿæˆä»¤ç‰Œ
        String token = loginService.login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(),
        String token = "";
        if (loginBody.getGrantType() != null && !"".equals(loginBody.getGrantType())) {
            token = loginService.loginByCode(loginBody.getSocialCode(), loginBody.getSocialState());
        } else {
            token = loginService.login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(),
                loginBody.getUuid());
        }
        ajax.put(Constants.TOKEN, token);
        return ajax;
    }
    /**
     * èŽ·å–ç”¨æˆ·ä¿¡æ¯
@@ -83,4 +95,17 @@
        List<SysMenu> menus = menuService.selectMenuTreeByUserId(userId);
        return AjaxResult.success(menuService.buildMenus(menus));
    }
    /**
     * èŽ·å–è·³è½¬URL
     *
     * @return ç»“æžœ
     */
    @GetMapping("/binding")
    public AjaxResult authBinding() {
        AuthRequest authRequest = SocialUtils.getAuthKeyloakRequest();
        String authorizeUrl = authRequest.authorize(Base64.encode(AuthStateUtils.createState(), StandardCharsets.UTF_8));
        return AjaxResult.success("操作成功", authorizeUrl);
    }
}
zhitan-admin/src/main/resources/application-dev.yml
@@ -6,8 +6,8 @@
    druid:
      # ä¸»åº“数据源
      master:
        url: jdbc:postgresql://127.0.0.1:5432/energy
        username: root
        url: jdbc:postgresql://127.0.0.1:5432/postgres
        username: postgres
        password: 123456
      # ä»Žåº“数据源
      slave:
@@ -59,3 +59,11 @@
        wall:
          config:
            multi-statement-allow: true
keycloak:
      # keycloak æœåŠ¡å™¨åœ°å€
  server-url: https://lanbaosystem.shlanbao.cn:8443
  realm: lanbao
  client-id: DataCapture
  client-secret: kplisa4lJHEIM6knqefVbxln85QbA5NX
  redirect-uri: http://192.168.12.236:80/social-callback
  scopes: [openid, email, phone, profile]
zhitan-admin/src/main/resources/application.yml
@@ -142,24 +142,24 @@
    # è¸¢å‡ºä¹‹å‰ç™»å½•çš„/之后登录的用户,默认踢出之前登录的用户
    kickoutAfter: false
rtdb:
  host: http://127.0.0.1:8086
  token: ==
  org: org
  bucket: bucket
  host: http://127.0.0.1:8086  # ç”¨æˆ·åroot å¯†ç 12345678
  token: AminQagYp5rjb09mFPYvriK0T0vlF-zmwboqtUzdcq3nkXNuhnEpMuG_Ht5vtfWC4xBIVOThvoxy5reTer9XcQ==
  org: lanbao
  bucket: nygl
  measurement: data
###################### MQTT #################################
mqtt:
  # æœåŠ¡å™¨åœ°å€
  host: tcp://127.0.0.1:1883
  host: tcp://lanpucloud.cn:1883
  # ID唯一
  clientId: MQTT_WK
  # ä¸»é¢˜ å¤šä¸ªä¸»é¢˜ç”¨é€—号(,)分割 #表示这个主题下面所有,topic1,topic2,topic2/topic22/#(默认会取第一个主题)
  topics: topic1
  # ç”¨æˆ·å
  username: admin
  username: tongjitang
  # å¯†ç 
  password: 1q2w3e4r.
  password: 123456
  # è¿žæŽ¥è¶…æ—¶
  timeout: 30
  # å¿ƒè·³æ£€æµ‹
zhitan-common/pom.xml
@@ -133,7 +133,40 @@
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
        </dependency>
<!--        <dependency>-->
<!--            <groupId>cn.hutool</groupId>-->
<!--            <artifactId>hutool-core</artifactId>-->
<!--            <version>5.8.21</version>-->
<!--        </dependency>-->
<!--        <dependency>-->
<!--            <groupId>cn.hutool</groupId>-->
<!--            <artifactId>hutool-http</artifactId>-->
<!--            <version>5.8.21</version>-->
<!--        </dependency>-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.28</version>
            <!-- å¼ºåˆ¶å£°æ˜Žç‰ˆæœ¬ä¼˜å…ˆçº§ -->
            <optional>true</optional>
        </dependency>
<!--        <dependency>-->
<!--            <groupId>cn.hutool</groupId>-->
<!--            <artifactId>hutool-extra</artifactId>-->
<!--            <version>5.8.21</version>-->
<!--        </dependency>-->
        <dependency>
            <groupId>me.zhyd.oauth</groupId>
            <artifactId>JustAuth</artifactId>
            <version>1.16.7</version>
        </dependency>
        <dependency>
            <groupId>io.github.linpeilie</groupId>
            <artifactId>mapstruct-plus-spring-boot-starter</artifactId>
            <version>1.4.6</version>
        </dependency>
    </dependencies>
</project>
zhitan-common/src/main/java/com/zhitan/common/config/keycloak/AuthKeycloakRequest.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,118 @@
package com.zhitan.common.config.keycloak;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.xkcoding.http.support.HttpHeader;
import com.zhitan.common.utils.JsonUtils;
import com.zhitan.common.utils.spring.SpringUtils;
import me.zhyd.oauth.cache.AuthStateCache;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.request.AuthDefaultRequest;
import me.zhyd.oauth.utils.HttpUtils;
import me.zhyd.oauth.utils.UrlBuilder;
/**
 * Keycloak OAuth2 è®¤è¯è¯·æ±‚
 */
public class AuthKeycloakRequest extends AuthDefaultRequest {
    public static final String SERVER_URL = SpringUtils.getProperty("keycloak.server-url");
    public static final String REALM = SpringUtils.getProperty("keycloak.realm");
    public AuthKeycloakRequest(AuthConfig config) {
        super(config, AuthKeycloakSource.KEYCLOAK);
    }
    public AuthKeycloakRequest(AuthConfig config, AuthStateCache authStateCache) {
        super(config, AuthKeycloakSource.KEYCLOAK, authStateCache);
    }
    @Override
    public AuthToken getAccessToken(AuthCallback authCallback) {
        String body = doPostAuthorizationCode(authCallback.getCode());
        Dict object = JsonUtils.parseMap(body);
        if (object.containsKey("error")) {
            throw new AuthException(object.getStr("error_description"));
        }
        if (object.containsKey("message")) {
            throw new AuthException(object.getStr("message"));
        }
        return AuthToken.builder()
            .accessToken(object.getStr("access_token"))
            .refreshToken(object.getStr("refresh_token"))
            .idToken(object.getStr("id_token"))
            .tokenType(object.getStr("token_type"))
            .expireIn(object.getInt("expires_in"))
            .build();
    }
    @Override
    public AuthUser getUserInfo(AuthToken authToken) {
        String body = doGetUserInfo(authToken);
        Dict object = JsonUtils.parseMap(body);
        if (object.containsKey("error")) {
            throw new AuthException(object.getStr("error_description"));
        }
        if (object.containsKey("message")) {
            throw new AuthException(object.getStr("message"));
        }
        return AuthUser.builder()
            .uuid(object.getStr("sub"))
            .username(object.getStr("preferred_username"))
            .nickname(object.getStr("name"))
            .email(object.getStr("email"))
            .token(authToken)
            .source(this.source.toString())
            .build();
    }
    @Override
    protected String doPostAuthorizationCode(String code) {
        HttpRequest request = HttpRequest.post(source.accessToken())
            .header("Authorization", "Basic " + Base64.encode(config.getClientId()+":"+config.getClientSecret()))
            .form("grant_type", "authorization_code")
            .form("code", code)
            .form("redirect_uri", config.getRedirectUri());
        HttpResponse response = request.execute();
        return response.body();
    }
    @Override
    protected String doGetUserInfo(AuthToken authToken) {
        try {
            return new HttpUtils(config.getHttpConfig()).get(source.userInfo(), null, new HttpHeader()
                .add("Content-Type", "application/json")
                .add("Authorization", "Bearer " + authToken.getAccessToken()), false).getBody();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    @Override
    public String authorize(String state) {
        return UrlBuilder.fromBaseUrl(super.authorize(state))
            .queryParam("scope","openid")
            .build();
    }
}
zhitan-common/src/main/java/com/zhitan/common/config/keycloak/AuthKeycloakSource.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,36 @@
package com.zhitan.common.config.keycloak;
import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.request.AuthDefaultRequest;
public enum AuthKeycloakSource implements AuthSource {
    KEYCLOAK {
        /**
         * æŽˆæƒçš„api
         */
        @Override
        public String authorize() {
            return String.format("%s/realms/%s/protocol/openid-connect/auth", AuthKeycloakRequest.SERVER_URL, AuthKeycloakRequest.REALM);
        }
        @Override
        public String accessToken() {
            return String.format("%s/realms/%s/protocol/openid-connect/token", AuthKeycloakRequest.SERVER_URL, AuthKeycloakRequest.REALM);
        }
        @Override
        public String userInfo() {
            return String.format("%s/realms/%s/protocol/openid-connect/userinfo", AuthKeycloakRequest.SERVER_URL, AuthKeycloakRequest.REALM);
        }
        public String logout() {
            return String.format("%s/realms/%s/protocol/openid-connect/logout", AuthKeycloakRequest.SERVER_URL, AuthKeycloakRequest.REALM);
        }
        @Override
        public Class<? extends AuthDefaultRequest> getTargetClass() {
            return AuthKeycloakRequest.class;
        }
    }
}
zhitan-common/src/main/java/com/zhitan/common/config/keycloak/AuthRedisStateCache.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,62 @@
package com.zhitan.common.config.keycloak;
import com.zhitan.common.core.redis.RedisCache;
import lombok.AllArgsConstructor;
import me.zhyd.oauth.cache.AuthStateCache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
/**
 * æŽˆæƒçŠ¶æ€ç¼“å­˜
 */
@AllArgsConstructor
@Component
public class AuthRedisStateCache implements AuthStateCache {
    @Autowired
    private final RedisCache redisCache;
    /**
     * å­˜å…¥ç¼“å­˜
     *
     * @param key   ç¼“å­˜key
     * @param value ç¼“存内容
     */
    @Override
    public void cache(String key, String value) {
        // æŽˆæƒè¶…æ—¶æ—¶é—´ é»˜è®¤ä¸‰åˆ†é’Ÿ
        redisCache.setCacheObject("social_auth_codes:" + key, value, 3, TimeUnit.MINUTES);
    }
    @Override
    public void cache(String s, String s1, long l) {
    }
    /**
     * èŽ·å–ç¼“å­˜å†…å®¹
     *
     * @param key ç¼“å­˜key
     * @return ç¼“存内容
     */
    @Override
    public String get(String key) {
        return redisCache.getCacheObject("social_auth_codes:" + key);
    }
    /**
     * æ˜¯å¦å­˜åœ¨key,如果对应key的value值已过期,也返回false
     *
     * @param key ç¼“å­˜key
     * @return true:存在key,并且value没过期;false:key不存在或者已过期
     */
    @Override
    public boolean containsKey(String key) {
        return redisCache.hasKey("social_auth_codes:" + key);
    }
}
zhitan-common/src/main/java/com/zhitan/common/config/keycloak/SocialLoginConfigProperties.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,81 @@
package com.zhitan.common.config.keycloak;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.List;
/**
 * ç¤¾äº¤ç™»å½•配置
 *
 * @author thiszhc
 */
@Data
@Component
@ConfigurationProperties(prefix = "keycloak")
public class SocialLoginConfigProperties {
    /**
     * åº”用 ID
     */
    private String clientId;
    /**
     * åº”用密钥
     */
    private String clientSecret;
    /**
     * å›žè°ƒåœ°å€
     */
    private String redirectUri;
    /**
     * æ˜¯å¦èŽ·å–unionId
     */
    private boolean unionId;
    /**
     * Coding ä¼ä¸šåç§°
     */
    private String codingGroupName;
    /**
     * æ”¯ä»˜å®å…¬é’¥
     */
    private String alipayPublicKey;
    /**
     * ä¼ä¸šå¾®ä¿¡åº”用ID
     */
    private String agentId;
    /**
     * stackoverflow api key
     */
    private String stackOverflowKey;
    /**
     * è®¾å¤‡ID
     */
    private String deviceId;
    /**
     * å®¢æˆ·ç«¯ç³»ç»Ÿç±»åž‹
     */
    private String clientOsType;
    /**
     * maxkey æœåŠ¡å™¨åœ°å€
     */
    private String serverUrl;
    /**
     * è¯·æ±‚范围
     */
    private List<String> scopes;
    private String realm;
}
zhitan-common/src/main/java/com/zhitan/common/core/domain/model/LoginBody.java
@@ -27,6 +27,47 @@
     */
    private String uuid;
    private String socialState;
    private String socialCode;
    private String source;
    private String grantType;
    public String getSocialState() {
        return socialState;
    }
    public void setSocialState(String socialState) {
        this.socialState = socialState;
    }
    public String getSocialCode() {
        return socialCode;
    }
    public void setSocialCode(String socialCode) {
        this.socialCode = socialCode;
    }
    public String getSource() {
        return source;
    }
    public void setSource(String source) {
        this.source = source;
    }
    public String getGrantType() {
        return grantType;
    }
    public void setGrantType(String grantType) {
        this.grantType = grantType;
    }
    public String getUsername()
    {
        return username;
zhitan-common/src/main/java/com/zhitan/common/utils/JsonUtils.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,170 @@
package com.zhitan.common.utils;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.exc.MismatchedInputException;
import com.zhitan.common.utils.spring.SpringUtils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
 * JSON å·¥å…·ç±»
 *
 * @author èŠ‹é“æºç 
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class JsonUtils {
    private static final ObjectMapper OBJECT_MAPPER = SpringUtils.getBean(ObjectMapper.class);
    public static ObjectMapper getObjectMapper() {
        return OBJECT_MAPPER;
    }
    /**
     * å°†å¯¹è±¡è½¬æ¢ä¸ºJSON格式的字符串
     *
     * @param object è¦è½¬æ¢çš„对象
     * @return JSON格式的字符串,如果对象为null,则返回null
     * @throws RuntimeException å¦‚果转换过程中发生JSON处理异常,则抛出运行时异常
     */
    public static String toJsonString(Object object) {
        if (ObjectUtil.isNull(object)) {
            return null;
        }
        try {
            return OBJECT_MAPPER.writeValueAsString(object);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }
    /**
     * å°†JSON格式的字符串转换为指定类型的对象
     *
     * @param text  JSON格式的字符串
     * @param clazz è¦è½¬æ¢çš„目标对象类型
     * @param <T>   ç›®æ ‡å¯¹è±¡çš„æ³›åž‹ç±»åž‹
     * @return è½¬æ¢åŽçš„对象,如果字符串为空则返回null
     * @throws RuntimeException å¦‚果转换过程中发生IO异常,则抛出运行时异常
     */
    public static <T> T parseObject(String text, Class<T> clazz) {
        if (StringUtils.isEmpty(text)) {
            return null;
        }
        try {
            return OBJECT_MAPPER.readValue(text, clazz);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    /**
     * å°†å­—节数组转换为指定类型的对象
     *
     * @param bytes å­—节数组
     * @param clazz è¦è½¬æ¢çš„目标对象类型
     * @param <T>   ç›®æ ‡å¯¹è±¡çš„æ³›åž‹ç±»åž‹
     * @return è½¬æ¢åŽçš„对象,如果字节数组为空则返回null
     * @throws RuntimeException å¦‚果转换过程中发生IO异常,则抛出运行时异常
     */
    public static <T> T parseObject(byte[] bytes, Class<T> clazz) {
        if (ArrayUtil.isEmpty(bytes)) {
            return null;
        }
        try {
            return OBJECT_MAPPER.readValue(bytes, clazz);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    /**
     * å°†JSON格式的字符串转换为指定类型的对象,支持复杂类型
     *
     * @param text          JSON格式的字符串
     * @param typeReference æŒ‡å®šç±»åž‹çš„TypeReference对象
     * @param <T>           ç›®æ ‡å¯¹è±¡çš„æ³›åž‹ç±»åž‹
     * @return è½¬æ¢åŽçš„对象,如果字符串为空则返回null
     * @throws RuntimeException å¦‚果转换过程中发生IO异常,则抛出运行时异常
     */
    public static <T> T parseObject(String text, TypeReference<T> typeReference) {
        if (StringUtils.isBlank(text)) {
            return null;
        }
        try {
            return OBJECT_MAPPER.readValue(text, typeReference);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    /**
     * å°†JSON格式的字符串转换为Dict对象
     *
     * @param text JSON格式的字符串
     * @return è½¬æ¢åŽçš„Dict对象,如果字符串为空或者不是JSON格式则返回null
     * @throws RuntimeException å¦‚果转换过程中发生IO异常,则抛出运行时异常
     */
    public static Dict parseMap(String text) {
        if (StringUtils.isBlank(text)) {
            return null;
        }
        try {
            return OBJECT_MAPPER.readValue(text, OBJECT_MAPPER.getTypeFactory().constructType(Dict.class));
        } catch (MismatchedInputException e) {
            // ç±»åž‹ä¸åŒ¹é…è¯´æ˜Žä¸æ˜¯json
            return null;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    /**
     * å°†JSON格式的字符串转换为Dict对象的列表
     *
     * @param text JSON格式的字符串
     * @return è½¬æ¢åŽçš„Dict对象的列表,如果字符串为空则返回null
     * @throws RuntimeException å¦‚果转换过程中发生IO异常,则抛出运行时异常
     */
    public static List<Dict> parseArrayMap(String text) {
        if (StringUtils.isBlank(text)) {
            return null;
        }
        try {
            return OBJECT_MAPPER.readValue(text, OBJECT_MAPPER.getTypeFactory().constructCollectionType(List.class, Dict.class));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    /**
     * å°†JSON格式的字符串转换为指定类型对象的列表
     *
     * @param text  JSON格式的字符串
     * @param clazz è¦è½¬æ¢çš„目标对象类型
     * @param <T>   ç›®æ ‡å¯¹è±¡çš„æ³›åž‹ç±»åž‹
     * @return è½¬æ¢åŽçš„对象的列表,如果字符串为空则返回空列表
     * @throws RuntimeException å¦‚果转换过程中发生IO异常,则抛出运行时异常
     */
    public static <T> List<T> parseArray(String text, Class<T> clazz) {
        if (StringUtils.isEmpty(text)) {
            return new ArrayList<>();
        }
        try {
            return OBJECT_MAPPER.readValue(text, OBJECT_MAPPER.getTypeFactory().constructCollectionType(List.class, clazz));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
zhitan-common/src/main/java/com/zhitan/common/utils/MapstructUtils.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,94 @@
package com.zhitan.common.utils;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import com.zhitan.common.utils.spring.SpringUtils;
import io.github.linpeilie.Converter;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import java.util.List;
import java.util.Map;
/**
 * Mapstruct å·¥å…·ç±»
 * <p>参考文档:<a href="https://mapstruct.plus/introduction/quick-start.html">mapstruct-plus</a></p>
 *
 *
 * @author Michelle.Chung
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class MapstructUtils {
    private final static Converter CONVERTER = SpringUtils.getBean(Converter.class);
    /**
     * å°† T ç±»åž‹å¯¹è±¡ï¼Œè½¬æ¢ä¸º desc ç±»åž‹çš„对象并返回
     *
     * @param source æ•°æ®æ¥æºå®žä½“
     * @param desc   æè¿°å¯¹è±¡ è½¬æ¢åŽçš„对象
     * @return desc
     */
    public static <T, V> V convert(T source, Class<V> desc) {
        if (ObjectUtil.isNull(source)) {
            return null;
        }
        if (ObjectUtil.isNull(desc)) {
            return null;
        }
        return CONVERTER.convert(source, desc);
    }
    /**
     * å°† T ç±»åž‹å¯¹è±¡ï¼ŒæŒ‰ç…§é…ç½®çš„æ˜ å°„字段规则,给 desc ç±»åž‹çš„对象赋值并返回 desc å¯¹è±¡
     *
     * @param source æ•°æ®æ¥æºå®žä½“
     * @param desc   è½¬æ¢åŽçš„对象
     * @return desc
     */
    public static <T, V> V convert(T source, V desc) {
        if (ObjectUtil.isNull(source)) {
            return null;
        }
        if (ObjectUtil.isNull(desc)) {
            return null;
        }
        return CONVERTER.convert(source, desc);
    }
    /**
     * å°† T ç±»åž‹çš„集合,转换为 desc ç±»åž‹çš„集合并返回
     *
     * @param sourceList æ•°æ®æ¥æºå®žä½“列表
     * @param desc       æè¿°å¯¹è±¡ è½¬æ¢åŽçš„对象
     * @return desc
     */
    public static <T, V> List<V> convert(List<T> sourceList, Class<V> desc) {
        if (ObjectUtil.isNull(sourceList)) {
            return null;
        }
        if (CollUtil.isEmpty(sourceList)) {
            return CollUtil.newArrayList();
        }
        return CONVERTER.convert(sourceList, desc);
    }
    /**
     * å°† Map è½¬æ¢ä¸º beanClass ç±»åž‹çš„集合并返回
     *
     * @param map       æ•°æ®æ¥æº
     * @param beanClass beanç±»
     * @return bean对象
     */
    public static <T> T convert(Map<String, Object> map, Class<T> beanClass) {
        if (MapUtil.isEmpty(map)) {
            return null;
        }
        if (ObjectUtil.isNull(beanClass)) {
            return null;
        }
        return CONVERTER.convert(map, beanClass);
    }
}
zhitan-common/src/main/java/com/zhitan/common/utils/SocialUtils.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,47 @@
package com.zhitan.common.utils;
import cn.hutool.core.util.ObjectUtil;
import com.zhitan.common.config.keycloak.AuthKeycloakRequest;
import com.zhitan.common.config.keycloak.AuthRedisStateCache;
import com.zhitan.common.config.keycloak.SocialLoginConfigProperties;
import com.zhitan.common.utils.spring.SpringUtils;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.request.AuthDingTalkRequest;
import me.zhyd.oauth.request.AuthRequest;
/**
 * è®¤è¯æŽˆæƒå·¥å…·ç±»
 *
 * @author thiszhc
 */
public class SocialUtils {
    private static final AuthRedisStateCache STATE_CACHE = SpringUtils.getBean(AuthRedisStateCache.class);
    private static final SocialLoginConfigProperties obj = SpringUtils.getBean(SocialLoginConfigProperties.class);
    @SuppressWarnings("unchecked")
    public static AuthResponse<AuthUser> loginAuth( String code, String state) throws AuthException {
        AuthRequest authRequest = getAuthKeyloakRequest();
        AuthCallback callback = new AuthCallback();
        callback.setCode(code);
        callback.setState(state);
        return authRequest.login(callback);
    }
    public static AuthKeycloakRequest getAuthKeyloakRequest( ) {
        AuthConfig.AuthConfigBuilder builder = AuthConfig.builder()
            .clientId(obj.getClientId())
            .clientSecret(obj.getClientSecret())
            .redirectUri(obj.getRedirectUri())
            .scopes(obj.getScopes());
        return new AuthKeycloakRequest(builder.build(), STATE_CACHE);
    }
}
zhitan-common/src/main/java/com/zhitan/common/utils/SpringUtils.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,63 @@
//package com.zhitan.common.utils;
//
//import cn.hutool.extra.spring.SpringUtil;
//import org.springframework.beans.factory.NoSuchBeanDefinitionException;
//import org.springframework.context.ApplicationContext;
//import org.springframework.core.env.Environment;
//import org.springframework.stereotype.Component;
//
///**
// * spring工具类
// *
// * @author Lion Li
// */
//@Component
//public final class SpringUtils extends SpringUtil {
//
//    /**
//     * å¦‚æžœBeanFactory包含一个与所给名称匹配的bean定义,则返回true
//     */
//    public static boolean containsBean(String name) {
//        return getBeanFactory().containsBean(name);
//    }
//
//    /**
//     * åˆ¤æ–­ä»¥ç»™å®šåå­—注册的bean定义是一个singleton还是一个prototype。
//     * å¦‚果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException)
//     */
//    public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException {
//        return getBeanFactory().isSingleton(name);
//    }
//
//    /**
//     * @return Class æ³¨å†Œå¯¹è±¡çš„类型
//     */
//    public static Class<?> getType(String name) throws NoSuchBeanDefinitionException {
//        return getBeanFactory().getType(name);
//    }
//
//    /**
//     * å¦‚果给定的bean名字在bean定义中有别名,则返回这些别名
//     */
//    public static String[] getAliases(String name) throws NoSuchBeanDefinitionException {
//        return getBeanFactory().getAliases(name);
//    }
//
//    /**
//     * èŽ·å–aop代理对象
//     */
//    @SuppressWarnings("unchecked")
//    public static <T> T getAopProxy(T invoker) {
//        return (T) getBean(invoker.getClass());
//    }
//
//
//    /**
//     * èŽ·å–spring上下文
//     */
//    public static ApplicationContext context() {
//        return getApplicationContext();
//    }
//
//
//}
zhitan-common/src/main/java/com/zhitan/common/utils/spring/SpringUtils.java
@@ -1,12 +1,15 @@
package com.zhitan.common.utils.spring;
import cn.hutool.extra.spring.SpringUtil;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import com.zhitan.common.utils.StringUtils;
@@ -16,7 +19,7 @@
 * @author zhitan
 */
@Component
public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware
public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware
{
    /** Spring应用上下文环境 */
    private static ConfigurableListableBeanFactory beanFactory;
@@ -47,6 +50,11 @@
    public static <T> T getBean(String name) throws BeansException
    {
        return (T) beanFactory.getBean(name);
    }
    public static String getProperty(String key)
    {
        return applicationContext == null ? null : applicationContext.getEnvironment().getProperty(key);
    }
    /**
@@ -155,4 +163,6 @@
    {
        return applicationContext.getEnvironment().getRequiredProperty(key);
    }
}
zhitan-framework/pom.xml
@@ -59,6 +59,8 @@
            </exclusions>
        </dependency>
        <!-- èŽ·å–ç³»ç»Ÿä¿¡æ¯ -->
        <dependency>
            <groupId>com.github.oshi</groupId>
@@ -79,6 +81,10 @@
            <groupId>org.springframework.integration</groupId>
            <artifactId>spring-integration-mqtt</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-core</artifactId>
        </dependency>
    </dependencies>
</project>
zhitan-framework/src/main/java/com/zhitan/framework/config/SecurityConfig.java
@@ -115,7 +115,7 @@
                // è¿‡æ»¤è¯·æ±‚
                .authorizeRequests()
                // å¯¹äºŽç™»å½•login æ³¨å†Œregister éªŒè¯ç captchaImage å…è®¸åŒ¿åè®¿é—®
                .antMatchers("/login", "/register", "/captchaImage").permitAll()
                .antMatchers("/login", "/register", "/captchaImage", "/binding").permitAll()
                // é™æ€èµ„源,可匿名访问
                .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
                .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
zhitan-framework/src/main/java/com/zhitan/framework/security/handle/LogoutSuccessHandlerImpl.java
@@ -5,6 +5,12 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.annotation.Resource;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.zhitan.system.domain.SysSocial;
import com.zhitan.system.service.ISysSocialService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
@@ -30,6 +36,16 @@
    @Resource
    private TokenService tokenService;
    @Value("${keycloak.server-url}")
    private String keycloakServerUrl;
    @Value("${keycloak.realm}")
    private String keycloakRealm;
    @Resource
    private ISysSocialService sysSocialService;
    /**
     * é€€å‡ºå¤„理
     * 
@@ -47,6 +63,21 @@
            tokenService.delLoginUser(loginUser.getToken());
            // è®°å½•用户退出日志
            AsyncManager.me().execute(AsyncFactory.recordLoginInfo(userName, Constants.LOGOUT, MessageUtils.message("user.logout.success")));
            SysSocial social = sysSocialService.selectByUserId(loginUser.getUserId());
            if (social == null) {
                return;
            }
            String logoutUrl = keycloakServerUrl + "/realms/" + keycloakRealm + "/protocol/openid-connect/logout";
            HttpRequest req = HttpRequest.get(logoutUrl)
                    .form("refresh_token", social.getRefreshToken())
                    .form("id_token_hint", social.getIdToken());
            HttpResponse rep = req.execute();
            if (rep.isOk()) {
                System.out.println("1234");
            }
        }
        ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.success(MessageUtils.message("user.logout.success"))));
    }
zhitan-framework/src/main/java/com/zhitan/framework/web/service/SysLoginService.java
@@ -1,5 +1,7 @@
package com.zhitan.framework.web.service;
import cn.hutool.core.collection.CollUtil;
import com.zhitan.common.config.keycloak.AuthKeycloakRequest;
import com.zhitan.common.constant.CacheConstants;
import com.zhitan.common.constant.Constants;
import com.zhitan.common.constant.UserConstants;
@@ -10,14 +12,23 @@
import com.zhitan.common.exception.user.*;
import com.zhitan.common.utils.DateUtils;
import com.zhitan.common.utils.MessageUtils;
import com.zhitan.common.utils.SocialUtils;
import com.zhitan.common.utils.StringUtils;
import com.zhitan.common.utils.ip.IpUtils;
import com.zhitan.framework.manager.AsyncManager;
import com.zhitan.framework.manager.factory.AsyncFactory;
import com.zhitan.framework.security.context.AuthenticationContextHolder;
import com.zhitan.framework.security.single.SingleAuthenticationToken;
import com.zhitan.system.domain.SysSocial;
import com.zhitan.system.domain.bo.SysSocialBo;
import com.zhitan.system.domain.vo.SysSocialVo;
import com.zhitan.system.service.ISysConfigService;
import com.zhitan.system.service.ISysSocialService;
import com.zhitan.system.service.ISysUserService;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
@@ -25,6 +36,7 @@
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
/**
 * ç™»å½•校验方法
@@ -48,6 +60,9 @@
    @Resource
    private ISysConfigService configService;
    @Resource
    private ISysSocialService sysSocialService;
    /**
     * ç™»å½•验证
@@ -97,6 +112,68 @@
        return tokenService.createToken(loginUser);
    }
    public String loginByCode(String code,String state)
    {
        AuthKeycloakRequest authRequest = SocialUtils.getAuthKeyloakRequest();
       // AuthToken accessToken = authRequest.getAccessToken(passwordLoginBody);
        AuthCallback callback = new AuthCallback();
        callback.setCode(code);
        callback.setState(state);
        AuthResponse<AuthUser> res = authRequest.login(callback);
        AuthUser authUserData = res.getData();
        // æ–°å¢žKEYCLOAK用户自动创建逻辑
        String authId = authUserData.getSource() + authUserData.getUuid();
        List<SysSocial> list = sysSocialService.selectByAuthId(authId);
        if (CollUtil.isEmpty(list)) {
            // è‡ªåŠ¨åˆ›å»ºæ–°ç”¨æˆ·
            SysUser newUser = new SysUser();
            newUser.setUserName(authUserData.getUsername());
            newUser.setEmail(authUserData.getEmail());
            newUser.setNickName(authUserData.getNickname());
            newUser.setPassword("Initial123@"); // åˆå§‹å¯†ç éœ€ç¬¦åˆå®‰å…¨ç­–ç•¥
            newUser.setStatus("0");
            userService.insertUser(newUser); // å‡è®¾å­˜åœ¨æ’入方法
            // åˆ›å»ºç¤¾äº¤ç»‘定记录
            SysSocialBo newSocial = new SysSocialBo();
            newSocial.setUserId(newUser.getUserId());
            newSocial.setUserName(newUser.getUserName());
            newSocial.setAuthId(authId);
            newSocial.setSource(authUserData.getSource());
            newSocial.setOpenId(authUserData.getUuid());
            newSocial.setAccessToken(authUserData.getToken().getAccessToken());
            newSocial.setRefreshToken(authUserData.getToken().getRefreshToken());
            newSocial.setIdToken(authUserData.getToken().getIdToken());
            sysSocialService.insertByBo(newSocial); // éœ€ç¡®ä¿æœåŠ¡æœ‰æ–°å¢žæ–¹æ³•
            // é‡æ–°æŸ¥è¯¢ç¡®ä¿æ•°æ®å¯ç”¨
            list = sysSocialService.selectByAuthId(authId);
        } else {
            // æ›´æ–°ç¤¾äº¤ç»‘定记录
            SysSocialBo socialBo = new SysSocialBo();
            socialBo.setId(list.get(0).getId());
            socialBo.setAccessToken(authUserData.getToken().getAccessToken());
            socialBo.setRefreshToken(authUserData.getToken().getRefreshToken());
            socialBo.setIdToken(authUserData.getToken().getIdToken());
            sysSocialService.updateByBo(socialBo);
        }
        list = sysSocialService.selectByAuthId(authUserData.getSource() + authUserData.getUuid());
        if (CollUtil.isEmpty(list)) {
            throw new ServiceException("你还没有绑定第三方账号,绑定后才可以登录!");
        }
        LoginUser loginUser = new LoginUser();
        loginUser.setUser(userService.selectUserById(list.get(0).getUserId()));
        loginUser.setUserId(list.get(0).getUserId());
        // ç”Ÿæˆtoken
        return tokenService.createToken(loginUser);
    }
    /**
     * æ ¡éªŒéªŒè¯ç 
     *
zhitan-system/src/main/java/com/zhitan/model/mapper/EnergyIndexMapper.java
@@ -102,4 +102,6 @@
  List<EnergyIndex> getIndexByCode(@Param("code")String code, @Param("nodeId")String nodeId);
  List<EnergyIndex> getIndexByMeterIdIndexCode(@Param("meterId") String meterId, @Param("indexCode") String indexCode, @Param("nodeId") String nodeId);
  int deleteByIndexId(String indexId);
}
zhitan-system/src/main/java/com/zhitan/model/service/IEnergyIndexService.java
@@ -134,4 +134,10 @@
     * @return
     */
    List<EnergyIndex> listDeviceIndex(String nodeId, String meterId);
    /**
     * é€šè¿‡id删除采集点
     */
    boolean deleteByIndexId(String indexId);
}
zhitan-system/src/main/java/com/zhitan/model/service/impl/EnergyIndexServiceImpl.java
@@ -361,4 +361,11 @@
        List<EnergyIndex> energyIndexList = energyIndexMapper.getIndexByMeterIdIndexCode(meterId,null,nodeId);
        return energyIndexList;
    }
    @Override
    public boolean deleteByIndexId(String indexId) {
        return energyIndexMapper.deleteByIndexId(indexId) > 0;
    }
}
zhitan-system/src/main/java/com/zhitan/system/domain/SysSocial.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,133 @@
package com.zhitan.system.domain;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
 * ç¤¾ä¼šåŒ–关系对象 sys_social
 *
 * @author thiszhc
 */
@Data
@TableName("sys_social")
public class SysSocial  {
    private static final long serialVersionUID = 1L;
    /**
     * ä¸»é”®
     */
    @TableId(value = "id")
    private Long id;
    /**
     * ç”¨æˆ·ID
     */
    private Long userId;
    /**
     * çš„唯一ID
     */
    private String authId;
    /**
     * ç”¨æˆ·æ¥æº
     */
    private String source;
    /**
     * ç”¨æˆ·çš„æŽˆæƒä»¤ç‰Œ
     */
    private String accessToken;
    /**
     * ç”¨æˆ·çš„æŽˆæƒä»¤ç‰Œçš„æœ‰æ•ˆæœŸï¼Œéƒ¨åˆ†å¹³å°å¯èƒ½æ²¡æœ‰
     */
    private int expireIn;
    /**
     * åˆ·æ–°ä»¤ç‰Œï¼Œéƒ¨åˆ†å¹³å°å¯èƒ½æ²¡æœ‰
     */
    private String refreshToken;
    /**
     * ç”¨æˆ·çš„ open id
     */
    private String openId;
    /**
     * æŽˆæƒçš„第三方账号
     */
    private String userName;
    /**
     * æŽˆæƒçš„第三方昵称
     */
    private String nickName;
    /**
     * æŽˆæƒçš„第三方邮箱
     */
    private String email;
    /**
     * æŽˆæƒçš„第三方头像地址
     */
    private String avatar;
    /**
     * å¹³å°çš„æŽˆæƒä¿¡æ¯ï¼Œéƒ¨åˆ†å¹³å°å¯èƒ½æ²¡æœ‰
     */
    private String accessCode;
    /**
     * ç”¨æˆ·çš„ unionid
     */
    private String unionId;
    /**
     * æŽˆäºˆçš„æƒé™ï¼Œéƒ¨åˆ†å¹³å°å¯èƒ½æ²¡æœ‰
     */
    private String scope;
    /**
     * ä¸ªåˆ«å¹³å°çš„æŽˆæƒä¿¡æ¯ï¼Œéƒ¨åˆ†å¹³å°å¯èƒ½æ²¡æœ‰
     */
    private String tokenType;
    /**
     * id token,部分平台可能没有
     */
    private String idToken;
    /**
     * å°ç±³å¹³å°ç”¨æˆ·çš„附带属性,部分平台可能没有
     */
    private String macAlgorithm;
    /**
     * å°ç±³å¹³å°ç”¨æˆ·çš„附带属性,部分平台可能没有
     */
    private String macKey;
    /**
     * ç”¨æˆ·çš„æŽˆæƒcode,部分平台可能没有
     */
    private String code;
    /**
     * Twitter平台用户的附带属性,部分平台可能没有
     */
    private String oauthToken;
    /**
     * Twitter平台用户的附带属性,部分平台可能没有
     */
    private String oauthTokenSecret;
}
zhitan-system/src/main/java/com/zhitan/system/domain/bo/SysSocialBo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,133 @@
package com.zhitan.system.domain.bo;
import com.zhitan.system.domain.SysSocial;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotNull;
/**
 * ç¤¾ä¼šåŒ–关系业务对象 sys_social
 *
 * @author Lion Li
 */
@Data
@NoArgsConstructor
public class SysSocialBo {
    /**
     * ä¸»é”®
     */
    private Long id;
    /**
     * è®¤è¯å”¯ä¸€ID
     */
    private String authId;
    /**
     * ç”¨æˆ·æ¥æº
     */
    private String source;
    /**
     * ç”¨æˆ·çš„æŽˆæƒä»¤ç‰Œ
     */
    private String accessToken;
    /**
     * ç”¨æˆ·çš„æŽˆæƒä»¤ç‰Œçš„æœ‰æ•ˆæœŸï¼Œéƒ¨åˆ†å¹³å°å¯èƒ½æ²¡æœ‰
     */
    private int expireIn;
    /**
     * åˆ·æ–°ä»¤ç‰Œï¼Œéƒ¨åˆ†å¹³å°å¯èƒ½æ²¡æœ‰
     */
    private String refreshToken;
    /**
     * å¹³å°å”¯ä¸€id
     */
    private String openId;
    /**
     * ç”¨æˆ·çš„ ID
     */
    private Long userId;
    /**
     * å¹³å°çš„æŽˆæƒä¿¡æ¯ï¼Œéƒ¨åˆ†å¹³å°å¯èƒ½æ²¡æœ‰
     */
    private String accessCode;
    /**
     * ç”¨æˆ·çš„ unionid
     */
    private String unionId;
    /**
     * æŽˆäºˆçš„æƒé™ï¼Œéƒ¨åˆ†å¹³å°å¯èƒ½æ²¡æœ‰
     */
    private String scope;
    /**
     * æŽˆæƒçš„第三方账号
     */
    private String userName;
    /**
     * æŽˆæƒçš„第三方昵称
     */
    private String nickName;
    /**
     * æŽˆæƒçš„第三方邮箱
     */
    private String email;
    /**
     * æŽˆæƒçš„第三方头像地址
     */
    private String avatar;
    /**
     * ä¸ªåˆ«å¹³å°çš„æŽˆæƒä¿¡æ¯ï¼Œéƒ¨åˆ†å¹³å°å¯èƒ½æ²¡æœ‰
     */
    private String tokenType;
    /**
     * id token,部分平台可能没有
     */
    private String idToken;
    /**
     * å°ç±³å¹³å°ç”¨æˆ·çš„附带属性,部分平台可能没有
     */
    private String macAlgorithm;
    /**
     * å°ç±³å¹³å°ç”¨æˆ·çš„附带属性,部分平台可能没有
     */
    private String macKey;
    /**
     * ç”¨æˆ·çš„æŽˆæƒcode,部分平台可能没有
     */
    private String code;
    /**
     * Twitter平台用户的附带属性,部分平台可能没有
     */
    private String oauthToken;
    /**
     * Twitter平台用户的附带属性,部分平台可能没有
     */
    private String oauthTokenSecret;
}
zhitan-system/src/main/java/com/zhitan/system/domain/vo/SysSocialVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,139 @@
package com.zhitan.system.domain.vo;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
 * ç¤¾ä¼šåŒ–关系视图对象 sys_social
 *
 * @author thiszhc
 */
@Data
public class SysSocialVo implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * ä¸»é”®
     */
    private Long id;
    /**
     * ç”¨æˆ·ID
     */
    private Long userId;
    /**
     * ç§Ÿæˆ·ID
     */
    private String tenantId;
    /**
     * çš„唯一ID
     */
    private String authId;
    /**
     * ç”¨æˆ·æ¥æº
     */
    private String source;
    /**
     * ç”¨æˆ·çš„æŽˆæƒä»¤ç‰Œ
     */
    private String accessToken;
    /**
     * ç”¨æˆ·çš„æŽˆæƒä»¤ç‰Œçš„æœ‰æ•ˆæœŸï¼Œéƒ¨åˆ†å¹³å°å¯èƒ½æ²¡æœ‰
     */
    private int expireIn;
    /**
     * åˆ·æ–°ä»¤ç‰Œï¼Œéƒ¨åˆ†å¹³å°å¯èƒ½æ²¡æœ‰
     */
    private String refreshToken;
    /**
     * ç”¨æˆ·çš„ open id
     */
    private String openId;
    /**
     * æŽˆæƒçš„第三方账号
     */
    private String userName;
    /**
     * æŽˆæƒçš„第三方昵称
     */
    private String nickName;
    /**
     * æŽˆæƒçš„第三方邮箱
     */
    private String email;
    /**
     * æŽˆæƒçš„第三方头像地址
     */
    private String avatar;
    /**
     * å¹³å°çš„æŽˆæƒä¿¡æ¯ï¼Œéƒ¨åˆ†å¹³å°å¯èƒ½æ²¡æœ‰
     */
    private String accessCode;
    /**
     * ç”¨æˆ·çš„ unionid
     */
    private String unionId;
    /**
     * æŽˆäºˆçš„æƒé™ï¼Œéƒ¨åˆ†å¹³å°å¯èƒ½æ²¡æœ‰
     */
    private String scope;
    /**
     * ä¸ªåˆ«å¹³å°çš„æŽˆæƒä¿¡æ¯ï¼Œéƒ¨åˆ†å¹³å°å¯èƒ½æ²¡æœ‰
     */
    private String tokenType;
    /**
     * id token,部分平台可能没有
     */
    private String idToken;
    /**
     * å°ç±³å¹³å°ç”¨æˆ·çš„附带属性,部分平台可能没有
     */
    private String macAlgorithm;
    /**
     * å°ç±³å¹³å°ç”¨æˆ·çš„附带属性,部分平台可能没有
     */
    private String macKey;
    /**
     * ç”¨æˆ·çš„æŽˆæƒcode,部分平台可能没有
     */
    private String code;
    /**
     * Twitter平台用户的附带属性,部分平台可能没有
     */
    private String oauthToken;
    /**
     * Twitter平台用户的附带属性,部分平台可能没有
     */
    private String oauthTokenSecret;
    /**
     * åˆ›å»ºæ—¶é—´
     */
    private Date createTime;
}
zhitan-system/src/main/java/com/zhitan/system/mapper/SysSocialMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,14 @@
package com.zhitan.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zhitan.system.domain.SysSocial;
import com.zhitan.system.domain.vo.SysSocialVo;
/**
 * ç¤¾ä¼šåŒ–关系Mapper接口
 *
 * @author thiszhc
 */
public interface SysSocialMapper extends BaseMapper<SysSocial> {
}
zhitan-system/src/main/java/com/zhitan/system/service/ISysSocialService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,35 @@
package com.zhitan.system.service;
import com.zhitan.system.domain.SysSocial;
import com.zhitan.system.domain.bo.SysSocialBo;
import com.zhitan.system.domain.vo.SysSocialVo;
import java.util.List;
/**
 * ç¤¾ä¼šåŒ–关系Service接口
 *
 * @author thiszhc
 */
public interface ISysSocialService {
    /**
     * æ–°å¢žæŽˆæƒå…³ç³»
     */
    Boolean insertByBo(SysSocialBo bo);
    /**
     * æ›´æ–°ç¤¾ä¼šåŒ–关系
     */
    Boolean updateByBo(SysSocialBo bo);
    /**
     * æ ¹æ® authId æŸ¥è¯¢
     */
    List<SysSocial> selectByAuthId(String authId);
    SysSocial selectByUserId(Long userId);
}
zhitan-system/src/main/java/com/zhitan/system/service/impl/SysSocialServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,82 @@
package com.zhitan.system.service.impl;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.zhitan.common.utils.MapstructUtils;
import com.zhitan.common.utils.StringUtils;
import com.zhitan.common.utils.bean.BeanUtils;
import com.zhitan.system.domain.SysSocial;
import com.zhitan.system.domain.bo.SysSocialBo;
import com.zhitan.system.domain.vo.SysSocialVo;
import com.zhitan.system.mapper.SysSocialMapper;
import com.zhitan.system.service.ISysSocialService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
/**
 * ç¤¾ä¼šåŒ–关系Service业务层处理
 *
 * @author thiszhc
 * @date 2023-06-12
 */
@RequiredArgsConstructor
@Service
public class SysSocialServiceImpl implements ISysSocialService {
    private final SysSocialMapper baseMapper;
    /**
     * æ–°å¢žç¤¾ä¼šåŒ–关系
     */
    @Override
    public Boolean insertByBo(SysSocialBo bo) {
        SysSocial add = new SysSocial();
        BeanUtils.copyProperties(bo, add);
        boolean flag = baseMapper.insert(add) > 0;
        if (flag) {
            if (add != null) {
                bo.setId(add.getId());
            } else {
                return false;
            }
        }
        return flag;
    }
    /**
     * æ›´æ–°ç¤¾ä¼šåŒ–关系
     */
    @Override
    public Boolean updateByBo(SysSocialBo bo) {
        SysSocial update = new SysSocial();
        BeanUtils.copyProperties(bo, update);
        return baseMapper.updateById(update) > 0;
    }
    /**
     * æ ¹æ® authId æŸ¥è¯¢ç”¨æˆ·ä¿¡æ¯
     *
     * @param authId è®¤è¯id
     * @return æŽˆæƒä¿¡æ¯
     */
    @Override
    public List<SysSocial> selectByAuthId(String authId) {
        List<SysSocial> sysSocials = baseMapper.selectList(new LambdaQueryWrapper<SysSocial>().eq(SysSocial::getAuthId, authId));
        // è½¬æ¢æˆVO
        return sysSocials;
    }
    @Override
    public SysSocial selectByUserId(Long userId) {
        SysSocial socialVo = baseMapper.selectOne(new LambdaQueryWrapper<SysSocial>().eq(SysSocial::getUserId, userId));
        return socialVo;
    }
}
zhitan-system/src/main/resources/mapper/model/EnergyIndexMapper.xml
@@ -309,6 +309,11 @@
      #{indexId}
    </foreach>;
  </delete>
  <delete id="deleteByIndexId">
    delete
    from energy_index
    where index_id = #{indexId}
  </delete>
  <select id="getEnergyIndexMeterByCodes" resultMap="EnergyIndexResult">
    select ei.index_id,
zhitan-vue/.env.development
@@ -6,6 +6,6 @@
# ç³»ç»Ÿ/开发环境
# test
VITE_APP_BASE_API = 'http://139.159.201.118:8201'
VITE_APP_BASE_API = '/dev-api'
# hangmingjun
zhitan-vue/src/api/login.js
@@ -19,6 +19,28 @@
  })
}
export function loginBySocial(data) {
  console.log("loginBySocial",data);
  return request({
    url: '/login',
    headers: {
      isToken: false,
      repeatSubmit: false
    },
    method: 'post',
    data: data
  })
}
// ç»‘定账号
export function authBinding() {
  return request({
    url: '/binding',
    method: 'get'
  });
}
// æ³¨å†Œæ–¹æ³•
export function register(data) {
  return request({
zhitan-vue/src/api/types.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,59 @@
/**
 * æ³¨å†Œ
 */
export type RegisterForm = {
  tenantId: string;
  username: string;
  password: string;
  confirmPassword?: string;
  code?: string;
  uuid?: string;
  userType?: string;
};
/**
 * ç™»å½•请求
 */
export interface LoginData {
  tenantId?: string;
  username?: string;
  password?: string;
  rememberMe?: boolean;
  socialCode?: string;
  socialState?: string;
  source?: string;
  code?: string;
  uuid?: string;
  clientId: string;
  grantType: string;
}
/**
 * ç™»å½•响应
 */
export interface LoginResult {
  access_token: string;
}
/**
 * éªŒè¯ç è¿”回
 */
export interface VerifyCodeResult {
  captchaEnabled: boolean;
  uuid?: string;
  img?: string;
}
/**
 * ç§Ÿæˆ·
 */
export interface TenantVO {
  companyName: string;
  domain: any;
  tenantId: string;
}
export interface TenantInfo {
  tenantEnabled: boolean;
  voList: TenantVO[];
}
zhitan-vue/src/layout/components/SocialCallback/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,97 @@
<template>
  <div v-loading="loading" class="social-callback"></div>
</template>
<script setup lang="ts">
import { login,loginBySocial } from '@/api/login';
import { setToken, getToken } from '@/utils/auth';
import { LoginData } from '@/api/types';
import {ElMessage} from "element-plus";
import {useRoute} from "vue-router";
import useUserStore from '@/store/modules/user'
const route = useRoute();
const loading = ref(true);
/**
 * æŽ¥æ”¶Route传递的参数
 * @param {Object} route.query.
 */
const code = route.query.code as string;
const state = route.query.state as string;
console.log("state", atob(state))
const source = route.query.source as string;
// const stateJson = JSON.parse(atob(state));
// const domain = stateJson.domain as string;
console.log("code", code)
const processResponse = async (res: any) => {
  if (res.code !== 200) {
    throw new Error(res.msg);
  }
  if (res.data !== null) {
    setToken(res.token);
    useUserStore().token = res.token;
  }
  ElMessage.success(res.msg);
  setTimeout(() => {
    location.href = '/index';
  }, 2000);
};
const handleError = (error: any) => {
  ElMessage.error(error.message);
  setTimeout(() => {
    location.href = '/index';
  }, 2000);
};
//
// const callbackByCode = async (data: LoginData) => {
//   try {
//     const res = await callback(data);
//     await processResponse(res);
//     loading.value = false;
//   } catch (error) {
//     handleError(error);
//   }
// };
const loginByCode = async (data: LoginData) => {
  try {
    const res = await loginBySocial(data);
    await processResponse(res);
    loading.value = false;
  } catch (error) {
    handleError(error);
  }
};
const init = async () => {
  // // å¦‚果域名不相等 åˆ™é‡å®šå‘处理
  // let host = window.location.host;
  // if (domain !== host) {
  //   let urlFull = new URL(window.location.href);
  //   urlFull.host = domain;
  //   window.location.href = urlFull.toString();
  //   return;
  // }
  const data: LoginData = {
    socialCode: code,
    socialState: state,
    source: 'keycloak',
    grantType: 'social'
  };
  await loginByCode(data);
};
onMounted(() => {
  nextTick(() => {
    init();
  });
});
</script>
zhitan-vue/src/permission.js
@@ -11,7 +11,7 @@
NProgress.configure({ showSpinner: false });
const whiteList = ['/login', '/register', '/energy']
const whiteList = ['/login', '/register', '/energy','/social-callback']
router.beforeEach((to, from, next) => {
  NProgress.start()
zhitan-vue/src/router/index.js
@@ -43,6 +43,11 @@
    hidden: true
  },
  {
    path: '/social-callback',
    component: () => import('@/layout/components/SocialCallback/index'),
    hidden: true
  },
  {
    path: '/register',
    component: () => import('@/views/register'),
    hidden: true
zhitan-vue/src/utils/request.js
@@ -27,6 +27,7 @@
  // æ˜¯å¦éœ€è¦é˜²æ­¢æ•°æ®é‡å¤æäº¤
  const isRepeatSubmit = (config.headers || {}).repeatSubmit === false
  if (getToken() && !isToken) {
    console.log('getToken', getToken())
    config.headers['Authorization'] = 'Bearer ' + getToken() // è®©æ¯ä¸ªè¯·æ±‚携带自定义token è¯·æ ¹æ®å®žé™…情况自行修改
  }
  // get请求映射params参数
zhitan-vue/src/views/login.vue
@@ -83,10 +83,11 @@
</template>
<script setup>
import { getCodeImg } from "@/api/login"
import { getCodeImg, authBinding } from "@/api/login"
import Cookies from "js-cookie"
import { encrypt, decrypt } from "@/utils/jsencrypt"
import useUserStore from "@/store/modules/user"
import {ElMessage} from "element-plus";
const userStore = useUserStore()
const route = useRoute()
@@ -125,13 +126,13 @@
const register = ref(false)
const redirect = ref(undefined)
watch(
  route,
  (newRoute) => {
    redirect.value = newRoute.query && newRoute.query.redirect
  },
  { immediate: true }
)
// watch(
//   route,
//   (newRoute) => {
//     redirect.value = newRoute.query && newRoute.query.redirect
//   },
//   { immediate: true }
// )
function handleLogin() {
  proxy.$refs.loginRef.validate((valid) => {
@@ -193,8 +194,27 @@
  }
}
/**
 * ç¬¬ä¸‰æ–¹ç™»å½•
 * @param type
 */
const doSocialLogin = () => {
  console.log("doSocialLogin")
  authBinding().then((res) => {
    console.log(res);
    if (res.code === 200) {
      // èŽ·å–æŽˆæƒåœ°å€è·³è½¬
      window.location.href = res.data;
    } else {
      ElMessage.error(res.msg);
    }
  });
};
// éžå•点登录放开下边两行,否则注释掉
getCode()
getCookie()
// å•点登录放开下边一行,否则注释掉
// doSocialLogin()
</script>
<style lang="scss" scoped>
zhitan-vue/src/views/modelconfiguration/collectindicator/collectIndicator.vue
@@ -171,6 +171,7 @@
function handleDialog(type) {
  if (type == "add") {
    dialogTitle = "新增采集参数模版"
    form.value = {}
  } else {
    dialogTitle = "修改采集参数模版"
  }
zhitan-vue/vite.config.js
@@ -4,7 +4,7 @@
// https://vitejs.dev/config/
export default defineConfig(({ mode, command }) => {
  mode = "production"
  mode = "development"
  const env = loadEnv(mode, process.cwd())
  console.log(mode, "==========env")
  const { VITE_APP_ENV } = env
@@ -33,7 +33,7 @@
      proxy: {
        // https://cn.vitejs.dev/config/#server-proxy
        "/dev-api": {
          target: "http://localhost",
          target: "http://localhost:8080",
          changeOrigin: true,
          rewrite: (p) => p.replace(/^\/dev-api/, ""),
        },