2 Commity 4e8cb07114 ... 8dae0f76ef

Autor SHA1 Wiadomość Data
  funjon 8dae0f76ef 智慧人社单点登陆。 1 tydzień temu
  funjon 3d9642f974 转成达梦数据 1 tydzień temu
22 zmienionych plików z 810 dodań i 18 usunięć
  1. 25 0
      jeecg-boot/jeecg-boot-module/jeecg-module-zjrs/pom.xml
  2. 24 0
      jeecg-boot/jeecg-boot-module/jeecg-module-zjrs/src/main/java/org/jeecg/modules/zjrs/sso/config/ZjrsConfig.java
  3. 51 0
      jeecg-boot/jeecg-boot-module/jeecg-module-zjrs/src/main/java/org/jeecg/modules/zjrs/sso/controller/LoginSSOController.java
  4. 33 0
      jeecg-boot/jeecg-boot-module/jeecg-module-zjrs/src/main/java/org/jeecg/modules/zjrs/sso/entity/VRsUsers.java
  5. 24 0
      jeecg-boot/jeecg-boot-module/jeecg-module-zjrs/src/main/java/org/jeecg/modules/zjrs/sso/job/UserInfoJob.java
  6. 12 0
      jeecg-boot/jeecg-boot-module/jeecg-module-zjrs/src/main/java/org/jeecg/modules/zjrs/sso/mapper/VRsUsersMapper.java
  7. 23 0
      jeecg-boot/jeecg-boot-module/jeecg-module-zjrs/src/main/java/org/jeecg/modules/zjrs/sso/mapper/xml/VRsUsersMapper.xml
  8. 19 0
      jeecg-boot/jeecg-boot-module/jeecg-module-zjrs/src/main/java/org/jeecg/modules/zjrs/sso/service/ILoginSSOService.java
  9. 24 0
      jeecg-boot/jeecg-boot-module/jeecg-module-zjrs/src/main/java/org/jeecg/modules/zjrs/sso/service/IVRsUsersService.java
  10. 161 0
      jeecg-boot/jeecg-boot-module/jeecg-module-zjrs/src/main/java/org/jeecg/modules/zjrs/sso/service/impl/LoginSSOServiceImpl.java
  11. 96 0
      jeecg-boot/jeecg-boot-module/jeecg-module-zjrs/src/main/java/org/jeecg/modules/zjrs/sso/service/impl/VRsUsersServiceImpl.java
  12. 1 0
      jeecg-boot/jeecg-boot-module/pom.xml
  13. 6 0
      jeecg-boot/jeecg-module-system/jeecg-system-start/pom.xml
  14. 5 5
      jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/application-dev.yml
  15. 11 7
      jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/application-dm8.yml
  16. 1 1
      jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/application.yml
  17. 4 4
      jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/jeecg/jeecg_config.properties
  18. 1 0
      jeecg-boot/pom.xml
  19. 19 0
      jeecgboot-vue3/src/api/sys/user.ts
  20. 15 1
      jeecgboot-vue3/src/router/routes/index.ts
  21. 222 0
      jeecgboot-vue3/src/views/sys/login/RsSSOLogin.vue
  22. 33 0
      jeecgboot-vue3/src/views/system/loginmini/SSOLogin.vue

+ 25 - 0
jeecg-boot/jeecg-boot-module/jeecg-module-zjrs/pom.xml

@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>jeecg-boot-parent</artifactId>
+        <relativePath>../../pom.xml</relativePath>
+        <groupId>org.jeecgframework.boot3</groupId>
+        <version>3.9.2</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>jeecg-module-zjrs</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.jeecgframework.boot3</groupId>
+            <artifactId>jeecg-boot-base-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.jeecgframework.boot3</groupId>
+            <artifactId>jeecg-system-biz</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 24 - 0
jeecg-boot/jeecg-boot-module/jeecg-module-zjrs/src/main/java/org/jeecg/modules/zjrs/sso/config/ZjrsConfig.java

@@ -0,0 +1,24 @@
+package org.jeecg.modules.zjrs.sso.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 智慧人社SSO配置类
+ */
+@Data
+@Configuration
+@ConfigurationProperties(prefix = "zjrs")
+public class ZjrsConfig {
+
+    private Sso sso = new Sso();
+
+    @Data
+    public static class Sso {
+        /**
+         * 用户默认密码(加密后)
+         */
+        private String userPwd = "cb362cfeefbf3d8d";
+    }
+}

+ 51 - 0
jeecg-boot/jeecg-boot-module/jeecg-module-zjrs/src/main/java/org/jeecg/modules/zjrs/sso/controller/LoginSSOController.java

@@ -0,0 +1,51 @@
+package org.jeecg.modules.zjrs.sso.controller;
+
+import com.alibaba.fastjson.JSONObject;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.servlet.http.HttpServletRequest;
+import lombok.extern.slf4j.Slf4j;
+import org.jeecg.common.api.vo.Result;
+import org.jeecg.config.shiro.IgnoreAuth;
+import org.jeecg.modules.zjrs.sso.service.ILoginSSOService;
+import org.jeecg.modules.system.entity.SysUser;
+import org.jeecg.modules.system.service.ISysUserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+@Slf4j
+@RestController
+@RequestMapping("/zjrs/login")
+@Tag(name = "人社登陆")
+public class LoginSSOController {
+
+    @Autowired
+    private ISysUserService sysUserService;
+
+    @Autowired
+    private ILoginSSOService loginSSOService;
+
+    @GetMapping("/getMessage")
+    @IgnoreAuth
+    @Operation(summary = "获取用户信息", description = "根据用户名获取用户信息")
+    public Result<String> getMessage(@RequestParam(name = "name") String name) {
+        SysUser sysUser = sysUserService.getUserByName(name);
+        log.info("微服务被调用:{} ", name);
+        return Result.OK(name);
+    }
+
+
+    @PostMapping("/ssoLoginByToken")
+    @IgnoreAuth
+    @Operation(summary = "SSO单点登录", description = "通过usertoken获取用户信息并完成登录")
+    public Result<JSONObject> ssoLoginByToken(@RequestBody JSONObject params, HttpServletRequest request) {
+        try {
+            String usertoken = params.getString("usertoken");
+            JSONObject result = loginSSOService.ssoLoginByToken(usertoken, request);
+            return Result.OK(result);
+        } catch (RuntimeException e) {
+            log.error("SSO登录失败", e);
+            return Result.error(e.getMessage());
+        }
+    }
+}

+ 33 - 0
jeecg-boot/jeecg-boot-module/jeecg-module-zjrs/src/main/java/org/jeecg/modules/zjrs/sso/entity/VRsUsers.java

@@ -0,0 +1,33 @@
+package org.jeecg.modules.zjrs.sso.entity;
+
+import java.io.Serializable;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+@Data
+@EqualsAndHashCode(callSuper = false)
+@Accessors(chain = true)
+@TableName("V_RS_USERS")
+public class VRsUsers implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId
+    @TableField("LOGINID")
+    private String loginid;
+
+    @TableField("OPERNAME")
+    private String opername;
+
+    @TableField("CONTACTPHONE")
+    private String contactphone;
+
+    @TableField("SGUSERID")
+    private String sguserid;
+}

+ 24 - 0
jeecg-boot/jeecg-boot-module/jeecg-module-zjrs/src/main/java/org/jeecg/modules/zjrs/sso/job/UserInfoJob.java

@@ -0,0 +1,24 @@
+package org.jeecg.modules.zjrs.sso.job;
+
+import lombok.extern.slf4j.Slf4j;
+import org.jeecg.modules.zjrs.sso.service.IVRsUsersService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+public class UserInfoJob {
+
+    @Autowired
+    private IVRsUsersService vRsUsersService;
+
+    @Scheduled(cron = "0 0 2 * * ?")
+    public void userToLocal() {
+        try {
+            vRsUsersService.syncUsersToLocal();
+        } catch (RuntimeException e) {
+            log.error("用户信息同步任务执行失败", e);
+        }
+    }
+}

+ 12 - 0
jeecg-boot/jeecg-boot-module/jeecg-module-zjrs/src/main/java/org/jeecg/modules/zjrs/sso/mapper/VRsUsersMapper.java

@@ -0,0 +1,12 @@
+package org.jeecg.modules.zjrs.sso.mapper;
+
+import org.jeecg.modules.zjrs.sso.entity.VRsUsers;
+import org.apache.ibatis.annotations.Param;
+import java.util.List;
+
+public interface VRsUsersMapper {
+
+    List<VRsUsers> selectAll();
+
+    VRsUsers selectByLoginid(@Param("loginid") String loginid);
+}

+ 23 - 0
jeecg-boot/jeecg-boot-module/jeecg-module-zjrs/src/main/java/org/jeecg/modules/zjrs/sso/mapper/xml/VRsUsersMapper.xml

@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.jeecg.modules.zjrs.sso.mapper.VRsUsersMapper">
+
+    <resultMap id="BaseResultMap" type="org.jeecg.modules.zjrs.sso.entity.VRsUsers">
+        <id column="LOGINID" property="loginid" />
+        <result column="OPERNAME" property="opername" />
+        <result column="CONTACTPHONE" property="contactphone" />
+        <result column="SGUSERID" property="sguserid" />
+    </resultMap>
+
+    <select id="selectAll" resultMap="BaseResultMap">
+        SELECT LOGINID, OPERNAME, CONTACTPHONE, SGUSERID 
+        FROM V_RS_USERS
+    </select>
+
+    <select id="selectByLoginid" resultMap="BaseResultMap">
+        SELECT LOGINID, OPERNAME, CONTACTPHONE, SGUSERID 
+        FROM V_RS_USERS
+        WHERE LOGINID = #{loginid}
+    </select>
+
+</mapper>

+ 19 - 0
jeecg-boot/jeecg-boot-module/jeecg-module-zjrs/src/main/java/org/jeecg/modules/zjrs/sso/service/ILoginSSOService.java

@@ -0,0 +1,19 @@
+package org.jeecg.modules.zjrs.sso.service;
+
+import com.alibaba.fastjson.JSONObject;
+import jakarta.servlet.http.HttpServletRequest;
+
+public interface ILoginSSOService {
+
+
+    /**
+     * SSO单点登录(完整版)
+     * 通过usertoken参数获取用户信息,生成完整的登录响应
+     *
+     * @param usertoken 用户token
+     * @param request   请求对象
+     * @return 登录结果(包含token、userInfo、departs等)
+     * @throws RuntimeException 登录失败时抛出异常
+     */
+    JSONObject ssoLoginByToken(String usertoken, HttpServletRequest request);
+}

+ 24 - 0
jeecg-boot/jeecg-boot-module/jeecg-module-zjrs/src/main/java/org/jeecg/modules/zjrs/sso/service/IVRsUsersService.java

@@ -0,0 +1,24 @@
+package org.jeecg.modules.zjrs.sso.service;
+
+import com.alibaba.fastjson.JSONObject;
+import org.jeecg.modules.system.entity.SysUser;
+import org.jeecg.modules.zjrs.sso.entity.VRsUsers;
+import java.util.List;
+
+public interface IVRsUsersService {
+
+    List<VRsUsers> getAllUsers();
+
+    VRsUsers getUserByLoginid(String loginid);
+
+    JSONObject UserTokenToLoginUserInfo(String token);
+
+    void syncUsersToLocal();
+
+    /**
+     * 同步单个用户到本地(支持新增和修改)
+     * @param vRsUsers 用户信息
+     * @return 同步后的SysUser
+     */
+    SysUser syncUserToLocal(VRsUsers vRsUsers);
+}

+ 161 - 0
jeecg-boot/jeecg-boot-module/jeecg-module-zjrs/src/main/java/org/jeecg/modules/zjrs/sso/service/impl/LoginSSOServiceImpl.java

@@ -0,0 +1,161 @@
+package org.jeecg.modules.zjrs.sso.service.impl;
+
+import com.alibaba.fastjson.JSONObject;
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletRequest;
+import lombok.extern.slf4j.Slf4j;
+import org.jeecg.common.api.vo.Result;
+import org.jeecg.common.constant.CommonConstant;
+import org.jeecg.common.system.util.JwtUtil;
+import org.jeecg.common.util.RedisUtil;
+import org.jeecg.common.util.oConvertUtils;
+import org.jeecg.config.JeecgBaseConfig;
+import org.jeecg.modules.zjrs.sso.entity.VRsUsers;
+import org.jeecg.modules.zjrs.sso.service.ILoginSSOService;
+import org.jeecg.modules.zjrs.sso.service.IVRsUsersService;
+import org.jeecg.modules.system.entity.SysDepart;
+import org.jeecg.modules.system.entity.SysUser;
+import org.jeecg.modules.system.service.ISysDepartService;
+import org.jeecg.modules.system.service.ISysDictService;
+import org.jeecg.modules.system.service.ISysUserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+
+@Slf4j
+@Service
+public class LoginSSOServiceImpl implements ILoginSSOService {
+
+    @Autowired
+    private ISysUserService sysUserService;
+
+    @Autowired
+    private ISysDepartService sysDepartService;
+
+    @Autowired
+    private ISysDictService sysDictService;
+
+    @Autowired
+    private IVRsUsersService vRsUsersService;
+
+    @Autowired
+    private JeecgBaseConfig jeecgBaseConfig;
+
+
+    @Resource
+    private RedisUtil redisUtil;
+
+
+    @Override
+    public JSONObject ssoLoginByToken(String usertoken, HttpServletRequest request) {
+        log.info("SSO单点登录开始,usertoken: {}", usertoken);
+
+        VRsUsers vRsUsers = vRsUsersService.getUserByLoginid(usertoken);
+        if (vRsUsers == null) {
+            log.warn("未找到用户信息,usertoken: {}", usertoken);
+            throw new RuntimeException("未找到用户信息");
+        }
+
+        String loginId = vRsUsers.getLoginid();
+        SysUser sysUser = sysUserService.getUserByName(loginId);
+
+        if (sysUser == null) {
+            log.info("SysUser表中未找到用户,开始自动同步用户信息,username: {}", loginId);
+            sysUser = vRsUsersService.syncUserToLocal(vRsUsers);
+            log.info("用户信息自动同步成功,username: {}", loginId);
+        }
+
+        Result<?> checkResult = sysUserService.checkUserIsEffective(sysUser);
+        if (!checkResult.isSuccess()) {
+            throw new RuntimeException(checkResult.getMessage());
+        }
+
+        return userInfo(sysUser, request, CommonConstant.CLIENT_TYPE_PC);
+    }
+
+    private JSONObject userInfo(SysUser sysUser, HttpServletRequest request, String clientType) {
+        String username = sysUser.getUsername();
+        String syspassword = sysUser.getPassword();
+        JSONObject obj = new JSONObject(new LinkedHashMap<>());
+
+        String token = JwtUtil.sign(username, syspassword, clientType);
+        redisUtil.set(CommonConstant.PREFIX_USER_TOKEN + token, token);
+        long expireTime = CommonConstant.CLIENT_TYPE_APP.equalsIgnoreCase(clientType)
+                ? JwtUtil.APP_EXPIRE_TIME * 2 / 1000
+                : JwtUtil.EXPIRE_TIME * 2 / 1000;
+        redisUtil.expire(CommonConstant.PREFIX_USER_TOKEN + token, expireTime);
+        obj.put("token", token);
+
+        handleSingleSignOn(username, token, clientType);
+
+        sysUserService.setLoginTenant(sysUser, obj, username, null);
+
+        obj.put("userInfo", sysUser);
+
+        List<SysDepart> departs = sysDepartService.queryUserDeparts(sysUser.getId());
+        obj.put("departs", departs);
+        if (departs == null || departs.size() == 0) {
+            obj.put("multi_depart", 0);
+            sysUserService.updateUserDepart(username, null, null);
+        } else if (departs.size() == 1) {
+            sysUserService.updateUserDepart(username, departs.get(0).getOrgCode(), null);
+            obj.put("multi_depart", 1);
+        } else {
+            SysUser sysUserById = sysUserService.getById(sysUser.getId());
+            String loginOrgCode = sysUser.getLoginOrgCode();
+            boolean orgCodeInDeparts = departs.stream().anyMatch(d -> sysUserById.getOrgCode() != null && sysUserById.getOrgCode().equalsIgnoreCase(d.getOrgCode()));
+            if (!orgCodeInDeparts) {
+                sysUserById.setOrgCode(null);
+            }
+            if (oConvertUtils.isEmpty(sysUserById.getOrgCode())) {
+                String orgCode = oConvertUtils.isNotEmpty(loginOrgCode) ? loginOrgCode : departs.get(0).getOrgCode();
+                sysUserService.updateUserDepart(username, orgCode, null);
+            } else {
+                String orgCode = sysUserById.getOrgCode();
+                if (oConvertUtils.isNotEmpty(loginOrgCode) && !orgCode.equalsIgnoreCase(loginOrgCode)) {
+                    sysUserService.updateUserDepart(username, loginOrgCode, null);
+                }
+            }
+            obj.put("multi_depart", 2);
+        }
+
+        String vue3Version = request.getHeader(CommonConstant.VERSION);
+        if (oConvertUtils.isEmpty(vue3Version)) {
+            obj.put("sysAllDictItems", sysDictService.queryAllDictItems());
+        }
+
+        log.info("SSO登录成功,username: {}", username);
+        return obj;
+    }
+
+    private void handleSingleSignOn(String username, String newToken, String clientType) {
+        if (jeecgBaseConfig.getFirewall() == null || jeecgBaseConfig.getFirewall().getIsConcurrent() == null || Boolean.TRUE.equals(jeecgBaseConfig.getFirewall().getIsConcurrent())) {
+            log.debug("并发登录已启用:用户[{}]在{}端允许多地同时登录", username, clientType);
+            return;
+        }
+
+        log.info("【并发登录限制已开启】 用户[{}]在{}端不允许多地同时登录", username, clientType);
+        String redisKeyPrefix;
+        if (CommonConstant.CLIENT_TYPE_APP.equalsIgnoreCase(clientType)) {
+            redisKeyPrefix = CommonConstant.PREFIX_USER_TOKEN_APP;
+        } else if (CommonConstant.CLIENT_TYPE_PHONE.equalsIgnoreCase(clientType)) {
+            redisKeyPrefix = CommonConstant.PREFIX_USER_TOKEN_PHONE;
+        } else {
+            redisKeyPrefix = CommonConstant.PREFIX_USER_TOKEN_PC;
+        }
+
+        String userTokenKey = redisKeyPrefix + username;
+        Object oldTokenObj = redisUtil.get(userTokenKey);
+        if (oldTokenObj != null && !oldTokenObj.equals(newToken)) {
+            String oldToken = oldTokenObj.toString();
+            redisUtil.del(CommonConstant.PREFIX_USER_TOKEN + oldToken);
+            redisUtil.set(CommonConstant.PREFIX_USER_TOKEN_ERROR_MSG + oldToken, "不允许同一账号多地同时登录,当前登录被踢掉!", 60 * 60);
+            log.info("【并发登录限制已开启】用户[{}]在{}端的旧登录已被踢下线!", username, clientType);
+        }
+
+        redisUtil.set(userTokenKey, newToken);
+        redisUtil.expire(userTokenKey, JwtUtil.EXPIRE_TIME * 2 / 1000);
+    }
+}

+ 96 - 0
jeecg-boot/jeecg-boot-module/jeecg-module-zjrs/src/main/java/org/jeecg/modules/zjrs/sso/service/impl/VRsUsersServiceImpl.java

@@ -0,0 +1,96 @@
+package org.jeecg.modules.zjrs.sso.service.impl;
+
+import com.alibaba.fastjson.JSONObject;
+import lombok.extern.slf4j.Slf4j;
+import org.jeecg.modules.system.entity.SysUser;
+import org.jeecg.modules.system.service.ISysUserService;
+import org.jeecg.modules.zjrs.sso.config.ZjrsConfig;
+import org.jeecg.modules.zjrs.sso.entity.VRsUsers;
+import org.jeecg.modules.zjrs.sso.mapper.VRsUsersMapper;
+import org.jeecg.modules.zjrs.sso.service.IVRsUsersService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+@Slf4j
+@Service
+public class VRsUsersServiceImpl implements IVRsUsersService {
+
+    @Autowired
+    private VRsUsersMapper vRsUsersMapper;
+
+    @Autowired
+    private ISysUserService sysUserService;
+
+    @Autowired
+    private ZjrsConfig zjrsConfig;
+
+    @Override
+    public List<VRsUsers> getAllUsers() {
+        return vRsUsersMapper.selectAll();
+    }
+
+    @Override
+    public VRsUsers getUserByLoginid(String loginid) {
+        return vRsUsersMapper.selectByLoginid(loginid);
+    }
+
+    @Override
+    public JSONObject UserTokenToLoginUserInfo(String token) {
+        JSONObject result = new JSONObject();
+        result.put("loginId", token);
+        result.put("token", token);
+        result.put("userName", "name");
+        return result;
+    }
+
+    @Override
+    public void syncUsersToLocal() {
+        log.info("开始执行用户信息同步任务");
+        try {
+            List<VRsUsers> users = getAllUsers();
+            log.info("从视图 V_RS_USERS 获取到 {} 条用户数据", users.size());
+
+            for (VRsUsers vRsUser : users) {
+                syncUserToLocal(vRsUser);
+            }
+            log.info("用户信息同步任务执行完成");
+        } catch (Exception e) {
+            log.error("用户信息同步任务执行失败", e);
+            throw new RuntimeException("用户信息同步失败", e);
+        }
+    }
+
+    @Override
+    public SysUser syncUserToLocal(VRsUsers vRsUsers) {
+        String loginid = vRsUsers.getLoginid();
+        String sguserid = vRsUsers.getSguserid();
+        String contactphone = vRsUsers.getContactphone();
+        String opername = vRsUsers.getOpername();
+
+        SysUser existingUser = sysUserService.getUserByName(loginid);
+
+        if (existingUser != null) {
+            existingUser.setId(sguserid);
+            existingUser.setPhone(contactphone);
+            existingUser.setRealname(opername);
+            existingUser.setPassword(zjrsConfig.getSso().getUserPwd());
+            sysUserService.updateById(existingUser);
+            log.info("更新用户信息: username={}, id={}, phone={}", loginid, sguserid, contactphone);
+            return existingUser;
+        } else {
+            SysUser newUser = new SysUser();
+            newUser.setId(sguserid);
+            newUser.setUsername(loginid);
+            newUser.setRealname(opername);
+            newUser.setPhone(contactphone);
+            newUser.setPassword(zjrsConfig.getSso().getUserPwd());
+            newUser.setStatus(1);
+            newUser.setDelFlag(0);
+            sysUserService.save(newUser);
+            log.info("新增用户信息: username={}, id={}, phone={}", loginid, sguserid, contactphone);
+            return newUser;
+        }
+    }
+}

+ 1 - 0
jeecg-boot/jeecg-boot-module/pom.xml

@@ -15,6 +15,7 @@
     <modules>
         <module>jeecg-module-demo</module>
         <module>jeecg-boot-module-airag</module>
+        <module>jeecg-module-zjrs</module>
     </modules>
 
 

+ 6 - 0
jeecg-boot/jeecg-module-system/jeecg-system-start/pom.xml

@@ -24,6 +24,12 @@
             <artifactId>jeecg-module-demo</artifactId>
             <version>${jeecgboot.version}</version>
         </dependency>
+        <!-- 人社模块 -->
+        <dependency>
+            <groupId>org.jeecgframework.boot3</groupId>
+            <artifactId>jeecg-module-zjrs</artifactId>
+            <version>${jeecgboot.version}</version>
+        </dependency>
         
         <!-- flyway 数据库自动升级 -->
         <dependency>

+ 5 - 5
jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/application-dev.yml

@@ -151,9 +151,9 @@ spring:
           slow-sql-millis: 5000
       datasource:
         master:
-          url: jdbc:mysql://127.0.0.1:3306/jeecg-boot?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
+          url: jdbc:mysql://192.168.0.77:3306/jeecg-boot2?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
           username: root
-          password: root
+          password: bowin@2023
           driver-class-name: com.mysql.cj.jdbc.Driver
 #        # shardingjdbc数据源
 #        sharding-db:
@@ -162,10 +162,10 @@ spring:
   #redis 配置
   data:
     redis:
-      database: 0
-      host: 127.0.0.1
+      database: 5
+      host: 192.168.0.53
       port: 6379
-      password:
+      password: Bowin123
 #mybatis plus 设置
 mybatis-plus:
   mapper-locations: classpath*:org/jeecg/**/xml/*Mapper.xml

+ 11 - 7
jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/application-dm8.yml

@@ -44,7 +44,7 @@ spring:
   quartz:
     job-store-type: jdbc
     jdbc:
-      initialize-schema: embedded
+      initialize-schema: never
     #定时任务启动开关,true-开  false-关
     auto-startup: true
     #延迟1秒启动定时任务
@@ -130,17 +130,17 @@ spring:
       datasource:
         # 重点是将数据源指向oracle 用compatibleMode=oracle即可
         master:
-          url: jdbc:dm://127.0.0.1:30236?schema=SYSDBA&compatibleMode=oracle&zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=utf-8
+          url: jdbc:dm://192.168.0.56:5236?schema=ZJRS_JEECG_BOOT&compatibleMode=oracle&zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=utf-8
           username: SYSDBA
-          password: SYSDBA
+          password: Bowin123456
           driverClassName: dm.jdbc.driver.DmDriver
   #redis 配置
   data:
     redis:
-      database: 0
-      host: 127.0.0.1
+      database: 6
+      host: 192.168.0.53
       port: 6379
-      password: ''
+      password: Bowin123
 #mybatis plus 设置
 mybatis-plus:
   mapper-locations: classpath*:org/jeecg/**/xml/*Mapper.xml
@@ -157,13 +157,14 @@ mybatis-plus:
 #    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
     # 返回类型为Map,显示null对应的字段
     call-setters-on-nulls: true
+    jdbc-type-for-null: 'CLOB'
 #jeecg专用配置
 minidao:
   base-package: org.jeecg.modules.jmreport.*,org.jeecg.modules.drag.*
 jeecg:
   # AI集成
   ai-chat:
-    enabled: true
+    enabled: false
     model: deepseek-chat
     apiKey: ??
     apiHost: https://api.deepseek.com/v1
@@ -355,3 +356,6 @@ justauth:
     type: default
     prefix: 'demo::'
     timeout: 1h
+zjrs:
+  sso:
+    userPwd: cb362cfeefbf3d8d

+ 1 - 1
jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/application.yml

@@ -4,4 +4,4 @@ spring:
   config:
     import: optional:classpath:config/application-liteflow.yml
   profiles:
-    active: '@profile.name@'
+    active: 'dm8'

+ 4 - 4
jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/jeecg/jeecg_config.properties

@@ -1,9 +1,9 @@
 # Module path to generate in the backend Java project
-project_path=F:\\gitcode\\JeecgBoot\\jeecg-boot\\jeecg-boot-module\\jeecg-module-demo
+project_path=D:\\工作项目\\智慧人社\\code\\zirs-jeecgBoot\jeecg-boot\jeecg-boot-module\jeecg-module-zjrs
 ## Path to generate in the frontend VUE3 project
-#ui_project_path=F:\\gitcode\\1jeecg-boot-github\\jeecgboot-vue3
+ui_project_path=D:\\工作项目\\智慧人社\\code\\zirs-jeecgBoot\\jeecgboot-vue3
 # Business package path
-bussi_package=org.jeecg.modules.demo
+bussi_package=org.jeecg.modules.zjrs
 
 #default code path
 #source_root_package=src
@@ -27,4 +27,4 @@ db_filed_convert=true
 page_search_filed_num=1
 #page_filter_fields
 page_filter_fields=create_time,create_by,update_time,update_by
-exclude_table=act_,ext_act_,design_,onl_,sys_,qrtz_
+exclude_table=act_,ext_act_,design_,onl_,sys_,qrtz_

+ 1 - 0
jeecg-boot/pom.xml

@@ -88,6 +88,7 @@
         <module>jeecg-boot-base-core</module>
         <module>jeecg-module-system</module>
 		<module>jeecg-boot-module</module>
+        <module>jeecg-boot-module/jeecg-module-zjrs</module>
     </modules>
 
 	<repositories>

+ 19 - 0
jeecgboot-vue3/src/api/sys/user.ts

@@ -43,6 +43,8 @@ enum Api {
   getLoginQrcode = '/sys/getLoginQrcode',
   //监控二维码扫描状态
   getQrcodeToken = '/sys/getQrcodeToken',
+  //智慧人社SSO登录
+  rsSsoLogin = '/zjrs/login/ssoLoginByToken',
 }
 
 /**
@@ -214,3 +216,20 @@ export async function validateCasLogin(params) {
   let url = Api.validateCasLogin;
   return defHttp.get({ url: url, params });
 }
+
+/**
+ * @description: 智慧人社SSO单点登录
+ * 通过usertoken获取用户信息并完成登录
+ */
+export function ssoLoginByToken(usertoken: string) {
+  return defHttp.post<LoginResultModel>(
+    {
+      url: Api.rsSsoLogin,
+      data: { usertoken }
+    },
+    {
+      errorMessageMode: 'none',
+      isTransformResponse: false
+    }
+  );
+}

+ 15 - 1
jeecgboot-vue3/src/router/routes/index.ts

@@ -64,5 +64,19 @@ export const TokenLoginRoute: AppRouteRecordRaw = {
     ignoreAuth: true,
   },
 };
+
+/**
+ * 智慧人社SSO单点登录页面
+ * 通过usertoken参数进行登录
+ */
+export const RsSSOLoginRoute: AppRouteRecordRaw = {
+  path: '/rs-sso-login',
+  name: 'RsSSOLoginRoute',
+  component: () => import('/@/views/sys/login/RsSSOLogin.vue'),
+  meta: {
+    title: '智慧人社SSO登录',
+    ignoreAuth: true,
+  },
+};
 // Basic routing without permission
-export const basicRoutes = [LoginRoute, RootRoute, ...mainOutRoutes, REDIRECT_ROUTE, PAGE_NOT_FOUND_ROUTE, TokenLoginRoute, Oauth2LoginRoute];
+export const basicRoutes = [LoginRoute, RootRoute, ...mainOutRoutes, REDIRECT_ROUTE, PAGE_NOT_FOUND_ROUTE, TokenLoginRoute, Oauth2LoginRoute, RsSSOLoginRoute];

+ 222 - 0
jeecgboot-vue3/src/views/sys/login/RsSSOLogin.vue

@@ -0,0 +1,222 @@
+<template>
+    <div class="app-loading">
+        <div class="app-loading-wrap">
+            <img src="/resource/img/logo.png" class="app-loading-logo" alt="Logo">
+            <div class="app-loading-dots">
+                <span class="dot dot-spin"><i></i><i></i><i></i><i></i></span>
+            </div>
+            <div class="app-loading-title">智慧人社 SSO 登录中...</div>
+        </div>
+    </div>
+</template>
+
+<script lang="ts">
+  /**
+   * 智慧人社 SSO 单点登录页面
+   * 通过 URL 参数 usertoken 进行登录
+   */
+  import { useRoute, useRouter } from 'vue-router';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { useUserStore } from '/@/store/modules/user';
+  import { useI18n } from '/@/hooks/web/useI18n';
+  import { ssoLoginByToken } from '/@/api/sys/user';
+  
+  export default {
+    name: "RsSSOLogin",
+    setup(){
+      const route = useRoute();
+      const router = useRouter();
+      const {createMessage, notification} = useMessage();
+      const {t} = useI18n();
+      const routeQuery:any = route.query;
+      
+      // 获取 URL 参数 usertoken
+      const usertoken = routeQuery['usertoken'];
+      if(!usertoken){
+        createMessage.warning('参数无效:缺少 usertoken');
+        return;
+      }
+      
+      const userStore = useUserStore();
+      
+      // 调用 SSO 登录接口
+      ssoLoginByToken(usertoken).then(res => {
+        console.log("SSO登录响应:", res);
+        if(res && res.success && res.result){
+          requestSuccess(res.result);
+        }else{
+          requestFailed(res);
+        }
+      }).catch(err => {
+        requestFailed(err);
+      });
+
+      function requestFailed (err) {
+        notification.error({
+          message: '登录失败',
+          description: ((err.response || {}).data || {}).message || err.message || "请求出现错误,请稍后再试",
+          duration: 4,
+        });
+      }
+      
+      function requestSuccess(res){
+        const { token, userInfo } = res;
+        
+        // 保存 token 和用户信息
+        userStore.setToken(token);
+        userStore.setUserInfo(userInfo);
+        
+        // 设置租户信息
+        if(userInfo && userInfo.loginTenantId){
+          userStore.setTenant(userInfo.loginTenantId);
+        }
+        
+        // 获取重定向地址
+        let redirect = routeQuery['redirect'] as string;
+        
+        // 代码逻辑说明: 修复登录成功后,没有正确重定向的问题
+        if (redirect) {
+          window.open(`${router.options.history.base}${redirect}`, '_self');
+        } else {
+          // 跳转到首页
+          router.replace((userInfo && userInfo.homePath) || '/');
+        }
+        
+        notification.success({
+          message: t('sys.login.loginSuccessTitle'),
+          description: `${t('sys.login.loginSuccessDesc')}: ${userInfo.realname || userInfo.username}`,
+          duration: 3,
+        });
+      }
+    }
+  }
+</script>
+
+<style scoped>
+    html[data-theme='dark'] .app-loading {
+        background-color: #2c344a;
+    }
+
+    html[data-theme='dark'] .app-loading .app-loading-title {
+        color: rgba(255, 255, 255, 0.85);
+    }
+
+    .app-loading {
+        display: flex;
+        width: 100%;
+        height: 100%;
+        justify-content: center;
+        align-items: center;
+        flex-direction: column;
+        background-color: #f4f7f9;
+    }
+
+    .app-loading .app-loading-wrap {
+        position: absolute;
+        top: 50%;
+        left: 50%;
+        display: flex;
+        -webkit-transform: translate3d(-50%, -50%, 0);
+        transform: translate3d(-50%, -50%, 0);
+        justify-content: center;
+        align-items: center;
+        flex-direction: column;
+    }
+
+    .app-loading .dots {
+        display: flex;
+        padding: 98px;
+        justify-content: center;
+        align-items: center;
+    }
+
+    .app-loading .app-loading-title {
+        display: flex;
+        margin-top: 30px;
+        font-size: 30px;
+        color: rgba(0, 0, 0, 0.85);
+        justify-content: center;
+        align-items: center;
+    }
+
+    .app-loading .app-loading-logo {
+        display: block;
+        width: 90px;
+        margin: 0 auto;
+        margin-bottom: 20px;
+    }
+
+    .dot {
+        position: relative;
+        display: inline-block;
+        width: 48px;
+        height: 48px;
+        margin-top: 30px;
+        font-size: 32px;
+        transform: rotate(45deg);
+        box-sizing: border-box;
+        animation: antRotate 1.2s infinite linear;
+    }
+
+    .dot i {
+        position: absolute;
+        display: block;
+        width: 20px;
+        height: 20px;
+        background-color: #0065cc;
+        border-radius: 100%;
+        opacity: 0.3;
+        transform: scale(0.75);
+        animation: antSpinMove 1s infinite linear alternate;
+        transform-origin: 50% 50%;
+    }
+
+    .dot i:nth-child(1) {
+        top: 0;
+        left: 0;
+    }
+
+    .dot i:nth-child(2) {
+        top: 0;
+        right: 0;
+        -webkit-animation-delay: 0.4s;
+        animation-delay: 0.4s;
+    }
+
+    .dot i:nth-child(3) {
+        right: 0;
+        bottom: 0;
+        -webkit-animation-delay: 0.8s;
+        animation-delay: 0.8s;
+    }
+
+    .dot i:nth-child(4) {
+        bottom: 0;
+        left: 0;
+        -webkit-animation-delay: 1.2s;
+        animation-delay: 1.2s;
+    }
+    @keyframes antRotate {
+        to {
+            -webkit-transform: rotate(405deg);
+            transform: rotate(405deg);
+        }
+    }
+    @-webkit-keyframes antRotate {
+        to {
+            -webkit-transform: rotate(405deg);
+            transform: rotate(405deg);
+        }
+    }
+    @keyframes antSpinMove {
+        to {
+            opacity: 1;
+        }
+    }
+    @-webkit-keyframes antSpinMove {
+        to {
+            opacity: 1;
+        }
+    }
+
+</style>

+ 33 - 0
jeecgboot-vue3/src/views/system/loginmini/SSOLogin.vue

@@ -0,0 +1,33 @@
+<template>
+  <div class="SSOLogin"></div>
+</template>
+
+<script setup lang="ts">
+  import { ref, toRaw } from 'vue';
+  import { useRouter } from 'vue-router';
+  //import { PageEnum } from '/@/enums/pageEnum';
+  import { router } from '/@/router';
+  import { rsSSOLogin } from '/@/api/sys/user';
+  //import { useI18n } from '/@/hooks/web/useI18n';
+  //const { t } = useI18n();
+  const { currentRoute } = useRouter();
+  const route = currentRoute.value;
+
+  const checkLogin = () => {
+    let token = route.params.usertoken;
+    rsSSOLogin({ token }).then((res) => {
+      if (res.success) {
+      }
+    });
+
+    if (userInfo) {
+      notification.success({
+        message: t('sys.login.loginSuccessTitle'),
+        description: `${t('sys.login.loginSuccessDesc')}: ${userInfo.realname}`,
+        duration: 3,
+      });
+    }
+  };
+</script>
+
+<style lang="less" scoped></style>