瀏覽代碼

对接钉钉以及大屏地址修改

ZHOUTD 1 年之前
父節點
當前提交
9895c6af43

+ 5 - 0
xzl-admin/pom.xml

@@ -16,6 +16,11 @@
     </description>
 
     <dependencies>
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>alibaba-dingtalk-service-sdk</artifactId>
+            <version>2.0.0</version>
+        </dependency>
         <dependency>
             <groupId>am.lodge</groupId>
             <artifactId>am-lodge-commons</artifactId>

+ 55 - 2
xzl-admin/src/main/java/com/xzl/web/controller/system/SysLoginController.java

@@ -1,7 +1,10 @@
 package com.xzl.web.controller.system;
 
-import java.util.List;
-import java.util.Set;
+import java.util.*;
+
+import com.dingtalk.api.response.OapiSnsGetuserinfoBycodeResponse;
+import com.xzl.web.mapper.UserPortraitMapper;
+import com.xzl.web.utils.DingTalkUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PostMapping;
@@ -34,6 +37,9 @@ public class SysLoginController
     @Autowired
     private SysPermissionService permissionService;
 
+    @Autowired
+    private UserPortraitMapper userPortraitMapper;
+
     /**
      * 登录方法
      * 
@@ -51,6 +57,53 @@ public class SysLoginController
         return ajax;
     }
 
+
+    /**
+     * 钉钉登录
+     *
+     * @param code 钉钉id
+     * @return 结果
+     */
+    @PostMapping("/ddLogin")
+    public Map<String, String> ddLogin(String code) {
+        Map<String, String> result = new HashMap<>();
+        Map<String, String> map = new HashMap<>();
+        try {
+            OapiSnsGetuserinfoBycodeResponse ddlogin = DingTalkUtils.ddLogin(code);
+            if (ddlogin == null) {
+                result.put("status","error");
+                result.put("msg","扫码异常,请重新扫码");
+                return result;
+            }
+            if (ddlogin.getUserInfo() == null) {
+                result.put("status","error");
+                result.put("msg","不是内部员工");
+                return result;
+            }
+            String unionId = ddlogin.getUserInfo().getUnionid();
+            SysUser user=userPortraitMapper.getUserByDingUnionId(unionId);
+            if(user==null||user.getUserName().isEmpty()){
+                //获取ddID
+                String ID = DingTalkUtils.getUserInfo(unionId).getUserid();
+                user=userPortraitMapper.getUserByDingUsername(ID);
+                if(user==null||user.getUserName().isEmpty()){
+                    result.put("status","error");
+                    result.put("msg","用户不存在");
+                    return result;
+                }
+
+            }
+            // 证明取到user信息了, 调用模拟登录
+            // 生成令牌
+            String token = loginService.login(user);
+            result.put("status","success");
+            result.put("msg",token);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+        return result;
+    }
+
     /**
      * 获取用户信息
      * 

+ 4 - 0
xzl-admin/src/main/java/com/xzl/web/mapper/UserPortraitMapper.java

@@ -1,5 +1,6 @@
 package com.xzl.web.mapper;
 
+import com.xzl.common.core.domain.entity.SysUser;
 import org.apache.ibatis.annotations.Mapper;
 
 import java.util.List;
@@ -7,4 +8,7 @@ import java.util.List;
 @Mapper
 public interface UserPortraitMapper {
     List<String> getWordCloudInfo();
+
+    SysUser getUserByDingUnionId(String unionId);
+    SysUser getUserByDingUsername(String username);
 }

+ 51 - 0
xzl-admin/src/main/java/com/xzl/web/utils/DingTalkUtils.java

@@ -0,0 +1,51 @@
+package com.xzl.web.utils;
+
+import com.alibaba.fastjson.JSON;
+import com.dingtalk.api.DefaultDingTalkClient;
+import com.dingtalk.api.DingTalkClient;
+import com.dingtalk.api.request.*;
+import com.dingtalk.api.response.*;
+import com.taobao.api.ApiException;
+
+
+/**
+ * @ClassName: DingTalkUtils
+ * @Description: 用于
+ * Modification History:
+ * Date                  Author                 Version       Description
+ * ---------------------------------------------------------
+ * 2023/10/16             ZhangShuling      v1.0.0
+ */
+public class DingTalkUtils {
+    private static final String APP_KEY = "dingr2toeanbqkmhitkx";
+    private static final String APP_SECRET = "IC_jU9Poawrq3TlWrfHhbtVEpsKtWo95J3AeCukKXTCui4bcLr8CYJ290lGsfeqR";
+
+    public static String getAccessToken() throws ApiException {
+        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/gettoken");
+        OapiGettokenRequest req = new OapiGettokenRequest();
+        req.setAppkey(APP_KEY);
+        req.setAppsecret(APP_SECRET);
+        req.setHttpMethod("GET");
+        OapiGettokenResponse rsp = client.execute(req);
+        return rsp.getAccessToken();
+    }
+
+    public static OapiSnsGetuserinfoBycodeResponse ddLogin(String code) throws Exception {
+        DefaultDingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/sns/getuserinfo_bycode");
+        OapiSnsGetuserinfoBycodeRequest req = new OapiSnsGetuserinfoBycodeRequest();
+        req.setTmpAuthCode(code);
+        OapiSnsGetuserinfoBycodeResponse response = client.execute(req, APP_KEY, APP_SECRET);
+        return response;
+
+    }
+
+    public static OapiUserGetUseridByUnionidResponse getUserInfo(String unionId) throws Exception {
+        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/user/getUseridByUnionid");
+        OapiUserGetUseridByUnionidRequest req = new OapiUserGetUseridByUnionidRequest();
+        req.setUnionid(unionId);
+        req.setHttpMethod("GET");
+        OapiUserGetUseridByUnionidResponse rsp = client.execute(req, getAccessToken());
+        return rsp;
+    }
+
+}

+ 11 - 0
xzl-admin/src/main/resources/mapper/UserPortraitMapper.xml

@@ -4,4 +4,15 @@
     <select id="getWordCloudInfo" parameterType="string" resultType="string">
         select distinct goods_name from d_order
     </select>
+
+
+    <select id="getUserByDingUnionId" parameterType="string" resultType="com.xzl.common.core.domain.entity.SysUser">
+        select user_name as "userName",password from sys_user where ding_union_id=#{unionId}
+    </select>
+
+    <select id="getUserByDingUsername" parameterType="string" resultType="com.xzl.common.core.domain.entity.SysUser">
+        select user_name as "userName",password from sys_user where user_name=#{username}
+    </select>
+
+
 </mapper>

+ 1 - 1
xzl-framework/src/main/java/com/xzl/framework/config/SecurityConfig.java

@@ -108,7 +108,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
       // 过滤请求
       .authorizeRequests()
       // 对于登录login 注册register 验证码captchaImage 允许匿名访问
-      .antMatchers("/login", "/register", "/captchaImage").permitAll()
+      .antMatchers("/login", "/register", "/captchaImage","/ddLogin").permitAll()
       // 用户画像
       .antMatchers("/user-portait/**").permitAll()
       //数据库ER图

+ 13 - 0
xzl-framework/src/main/java/com/xzl/framework/web/service/SysLoginService.java

@@ -6,6 +6,8 @@ import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.authentication.BadCredentialsException;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 import org.springframework.security.core.Authentication;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
 import org.springframework.stereotype.Component;
 import com.xzl.common.constant.CacheConstants;
 import com.xzl.common.constant.Constants;
@@ -52,6 +54,9 @@ public class SysLoginService
     @Autowired
     private ISysConfigService configService;
 
+    @Autowired
+    UserDetailsServiceImpl userDetailsService;
+
     /**
      * 登录验证
      * 
@@ -178,4 +183,12 @@ public class SysLoginService
         sysUser.setLoginDate(DateUtils.getNowDate());
         userService.updateUserProfile(sysUser);
     }
+
+    public String login(SysUser sysUser) {
+        LoginUser loginUser = (LoginUser)userDetailsService.createLoginUser(sysUser);
+        AsyncManager.me().execute(AsyncFactory.recordLogininfor(sysUser.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
+        recordLoginInfo(loginUser.getUserId());
+        // 生成token
+        return tokenService.createToken(loginUser);
+    }
 }

二進制
xzl-ui/src/assets/images/dd.png


二進制
xzl-ui/src/assets/images/i2.png


二進制
xzl-ui/src/assets/images/i4.png


+ 1 - 1
xzl-ui/src/permission.js

@@ -8,7 +8,7 @@ import { isRelogin } from '@/utils/request'
 
 NProgress.configure({ showSpinner: false })
 
-const whiteList = ['/login', '/register']
+const whiteList = ['/login', '/register','/dingtalk']
 
 router.beforeEach((to, from, next) => {
   NProgress.start()

+ 5 - 0
xzl-ui/src/router/index.js

@@ -61,6 +61,11 @@ export const constantRoutes = [
     component: () => import('@/views/error/401'),
     hidden: true
   },
+  {
+    path: '/dingtalk',
+    component: () => import('@/views/dingtalk'),
+    hidden: true
+  },
   {
     path: '',
     component: Layout,

+ 9 - 0
xzl-ui/src/store/modules/user.js

@@ -50,6 +50,15 @@ const user = {
       })
     },
 
+    // 登录
+    DingTalkLogin({ commit }, token) {
+      return new Promise((resolve, reject) => {
+        setToken(token)
+        commit('SET_TOKEN',token)
+        resolve()
+      })
+    },
+
     // 获取用户信息
     GetInfo({ commit, state }) {
       return new Promise((resolve, reject) => {

+ 1 - 1
xzl-ui/src/views/deptScreen.vue

@@ -1,6 +1,6 @@
 <template>
   <div id="screen" >
-    <iframe frameborder="no"  src="http://10.70.192.123/webroot/decision/view/form?viewlet=大屏/专卖看板.frm" width="100%" height="100%"></iframe>
+    <iframe frameborder="no"  src="http://10.70.192.123/webroot/decision/view/form?viewlet=大屏新版20240226/专卖看板.frm" width="100%" height="100%"></iframe>
   </div>
 </template>
 <script>

+ 71 - 0
xzl-ui/src/views/dingtalk.vue

@@ -0,0 +1,71 @@
+<script setup>
+
+</script>
+
+<template>
+
+</template>
+<script>
+import request from "@/utils/request";
+import {setToken} from "@/utils/auth";
+export default {
+  name: "Login",
+  data() {
+    return {
+      redirect:undefined
+    }
+  },
+  methods:{
+    login(){
+      var formData=new FormData();
+      const t=this;
+      formData.append("code",this.$route.query.code);
+      request({
+        url:"/ddLogin",
+        method: "post",
+        data: formData
+      }).then(rs=>{
+        if(rs.status=="success") {
+          this.$store.dispatch("DingTalkLogin", rs.token).then(() => {
+            this.$router.push({path: this.redirect || "/"}).catch(() => {
+            });
+          }).catch(() => {
+          });
+        }else if (rs.status=="error"){
+          this.$message({message:rs.msg,type:rs.status})
+          this.$router.push({path: this.redirect || "/"}).catch(() => {
+          });
+        }
+      })
+    }
+  },
+  mounted() {
+    this.login()
+  }
+}
+
+function getParams(){
+  var name="code";
+  // 查询参数:先通过search取值,如果取不到就通过hash来取
+  var after = window.location.search;
+  after = after.substr(1) || window.location.hash.split('?')[1];
+  // 地址栏URL没有查询参数,返回空
+  if (!after) return null;
+  // 如果查询参数中没有"name",返回空
+  if (after.indexOf(name) === -1) return null;
+  var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)');
+  // 当地址栏参数存在中文时,需要解码,不然会乱码
+  var r = decodeURI(after).match(reg);
+  // 如果url中"code"没有值,返回空
+  if (!r) return null;
+  return r[2];
+}
+</script>
+
+
+
+
+
+<style scoped lang="scss">
+
+</style>

+ 125 - 64
xzl-ui/src/views/login.vue

@@ -1,70 +1,83 @@
 <template>
-  <div class="login">
+  <div class="login" style="">
     <el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form">
+      <img id="switch" class="switch" ref="switch" :src="img"
+           style="cursor: pointer;width: 60px;height: 60px;position: absolute;right: 0;top: 0;">
       <h3 class="title">用户画像管理系统</h3>
-      <el-form-item prop="username">
-        <el-input
-          v-model="loginForm.username"
-          type="text"
-          auto-complete="off"
-          placeholder="账号"
-        >
-          <svg-icon slot="prefix" icon-class="user" class="el-input__icon input-icon" />
-        </el-input>
-      </el-form-item>
-      <el-form-item prop="password">
-        <el-input
-          v-model="loginForm.password"
-          type="password"
-          auto-complete="off"
-          placeholder="密码"
-          @keyup.enter.native="handleLogin"
-        >
-          <svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon" />
-        </el-input>
-      </el-form-item>
-      <el-form-item prop="code" v-if="captchaEnabled">
-        <el-input
-          v-model="loginForm.code"
-          auto-complete="off"
-          placeholder="验证码"
-          style="width: 63%"
-          @keyup.enter.native="handleLogin"
-        >
-          <svg-icon slot="prefix" icon-class="validCode" class="el-input__icon input-icon" />
-        </el-input>
-        <div class="login-code">
-          <img :src="codeUrl" @click="getCode" class="login-code-img"/>
-        </div>
-      </el-form-item>
-      <el-checkbox v-model="loginForm.rememberMe" style="margin:0px 0px 25px 0px;">记住密码</el-checkbox>
-      <el-form-item style="width:100%;">
-        <el-button
-          :loading="loading"
-          size="medium"
-          type="primary"
-          style="width:100%;"
-          @click.native.prevent="handleLogin"
-        >
-          <span v-if="!loading">登 录</span>
-          <span v-else>登 录 中...</span>
-        </el-button>
-        <div style="float: right;" v-if="register">
-          <router-link class="link-type" :to="'/register'">立即注册</router-link>
-        </div>
-      </el-form-item>
+      <div id="passLogin" v-if="isPassLogin">
+        <el-form-item prop="username">
+          <el-input
+            v-model="loginForm.username"
+            type="text"
+            auto-complete="off"
+            placeholder="账号"
+          >
+            <svg-icon slot="prefix" icon-class="user" class="el-input__icon input-icon"/>
+          </el-input>
+        </el-form-item>
+        <el-form-item prop="password">
+          <el-input
+            v-model="loginForm.password"
+            type="password"
+            auto-complete="off"
+            placeholder="密码"
+            @keyup.enter.native="handleLogin"
+          >
+            <svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon"/>
+          </el-input>
+        </el-form-item>
+        <el-form-item prop="code" v-if="captchaEnabled">
+          <el-input
+            v-model="loginForm.code"
+            auto-complete="off"
+            placeholder="验证码"
+            style="width: 63%"
+            @keyup.enter.native="handleLogin"
+          >
+            <svg-icon slot="prefix" icon-class="validCode" class="el-input__icon input-icon"/>
+          </el-input>
+          <div class="login-code">
+            <img :src="codeUrl" @click="getCode" class="login-code-img"/>
+          </div>
+        </el-form-item>
+        <el-checkbox v-model="loginForm.rememberMe" style="margin:0px 0px 25px 0px;">记住密码</el-checkbox>
+        <el-form-item style="width:100%;">
+          <el-button
+            :loading="loading"
+            size="medium"
+            type="primary"
+            style="width:100%;"
+            @click.native.prevent="handleLogin"
+          >
+            <span v-if="!loading">登 录</span>
+            <span v-else>登 录 中...</span>
+          </el-button>
+          <div style="float: right;" v-if="register">
+            <router-link class="link-type" :to="'/register'">立即注册</router-link>
+          </div>
+        </el-form-item>
+      </div>
+      <div id="codeLogin" v-if="!isPassLogin" style="height: 364px">
+        <iframe id="dingtalk-qrcode" class="qrCode" :src="iframeSrc"
+                style="display: block;margin: auto;height: 293px;border: 1px solid #cecece;border-radius: 10px">
+        </iframe>
+        <p class="text" style="display: flex;align-items: center;justify-content: center;margin-top: 25px;">
+          <img :src="ddImg">
+          <span style="margin-left: 8px;">钉钉扫码登录</span>
+        </p>
+      </div>
     </el-form>
     <!--  底部  -->
     <div class="el-login-footer" style="display:none">
       <span>Copyright © 2018-2023 xzl.vip All Rights Reserved.</span>
     </div>
+    <iframe id="authFrame" style="display: none"/>
   </div>
 </template>
-
 <script>
-import { getCodeImg } from "@/api/login";
+import {getCodeImg} from "@/api/login";
 import Cookies from "js-cookie";
-import { encrypt, decrypt } from '@/utils/jsencrypt'
+import {encrypt, decrypt} from '@/utils/jsencrypt'
 
 export default {
   name: "Login",
@@ -80,24 +93,32 @@ export default {
       },
       loginRules: {
         username: [
-          { required: true, trigger: "blur", message: "请输入您的账号" }
+          {required: true, trigger: "blur", message: "请输入您的账号"}
         ],
         password: [
-          { required: true, trigger: "blur", message: "请输入您的密码" }
+          {required: true, trigger: "blur", message: "请输入您的密码"}
         ],
-        code: [{ required: true, trigger: "change", message: "请输入验证码" }]
+        code: [{required: true, trigger: "change", message: "请输入验证码"}]
       },
       loading: false,
       // 验证码开关
       captchaEnabled: true,
       // 注册开关
       register: false,
-      redirect: undefined
+      redirect: undefined,
+      img: require("@/assets/images/i2.png"),
+      ddImg: require("@/assets/images/dd.png"),
+      isPassLogin: false,
+      dingTalkConfig: {
+        appid: "dingr2toeanbqkmhitkx",//自己申请的appid
+        redirectUrl: "http://127.0.0.1/dingtalk",//这里是扫码成功后跳转的回调地址
+      },
+      iframeSrc: ""
     };
   },
   watch: {
     $route: {
-      handler: function(route) {
+      handler: function (route) {
         this.redirect = route.query && route.query.redirect;
       },
       immediate: true
@@ -106,8 +127,37 @@ export default {
   created() {
     this.getCode();
     this.getCookie();
+    this.iframeSrc = this.generateDingTalkQrcode();
+    this.addDingListener();
   },
   methods: {
+    addDingListener() {
+      let handleLoginTmpCode = (loginTmpCode) => {
+          console.log('loginTmpCode : ' + loginTmpCode)
+          window.location.href = this.getAuthUrl() + `&loginTmpCode=${loginTmpCode}`;
+      };
+      let handleMessage = (event) => {
+        if (event.origin === "https://login.dingtalk.com") {
+          handleLoginTmpCode(event.data);
+        }
+      };
+      if (typeof window.addEventListener != "undefined") {
+        window.addEventListener("message", handleMessage, false);
+      } else if (typeof window.attachEvent != "undefined") {
+        window.attachEvent("onmessage", handleMessage);
+      }
+    },
+    getAuthUrl() {
+      var redirectUrl = encodeURIComponent(this.dingTalkConfig.redirectUrl)
+      return 'https://oapi.dingtalk.com/connect/oauth2/sns_authorize?appid=' + this.dingTalkConfig.appid + '&response_type=code&scope=snsapi_login&state=STATE&redirect_uri=' + redirectUrl;
+    },
+    generateDingTalkQrcode() {
+      return 'https://login.dingtalk.com/login/qrcode.htm?goto=' + encodeURIComponent(this.getAuthUrl())
+    },
+    switch() {
+      this.img = require("@/assets/images/i4.png");
+      this.isPassLogin = !this.isPassLogin;
+    },
     getCode() {
       getCodeImg().then(res => {
         this.captchaEnabled = res.captchaEnabled === undefined ? true : res.captchaEnabled;
@@ -132,16 +182,17 @@ export default {
         if (valid) {
           this.loading = true;
           if (this.loginForm.rememberMe) {
-            Cookies.set("username", this.loginForm.username, { expires: 30 });
-            Cookies.set("password", encrypt(this.loginForm.password), { expires: 30 });
-            Cookies.set('rememberMe', this.loginForm.rememberMe, { expires: 30 });
+            Cookies.set("username", this.loginForm.username, {expires: 30});
+            Cookies.set("password", encrypt(this.loginForm.password), {expires: 30});
+            Cookies.set('rememberMe', this.loginForm.rememberMe, {expires: 30});
           } else {
             Cookies.remove("username");
             Cookies.remove("password");
             Cookies.remove('rememberMe');
           }
           this.$store.dispatch("Login", this.loginForm).then(() => {
-            this.$router.push({ path: this.redirect || "/" }).catch(()=>{});
+            this.$router.push({path: this.redirect || "/"}).catch(() => {
+            });
           }).catch(() => {
             this.loading = false;
             if (this.captchaEnabled) {
@@ -164,6 +215,7 @@ export default {
   background-image: url("../assets/images/login-background.jpg");
   background-size: cover;
 }
+
 .title {
   margin: 0px auto 30px auto;
   text-align: center;
@@ -171,36 +223,44 @@ export default {
 }
 
 .login-form {
+  position: absolute;
   border-radius: 6px;
   background: #ffffff;
   width: 400px;
   padding: 25px 25px 5px 25px;
+
   .el-input {
     height: 38px;
+
     input {
       height: 38px;
     }
   }
+
   .input-icon {
     height: 39px;
     width: 14px;
     margin-left: 2px;
   }
 }
+
 .login-tip {
   font-size: 13px;
   text-align: center;
   color: #bfbfbf;
 }
+
 .login-code {
   width: 33%;
   height: 38px;
   float: right;
+
   img {
     cursor: pointer;
     vertical-align: middle;
   }
 }
+
 .el-login-footer {
   height: 40px;
   line-height: 40px;
@@ -213,6 +273,7 @@ export default {
   font-size: 12px;
   letter-spacing: 1px;
 }
+
 .login-code-img {
   height: 38px;
 }

+ 1 - 1
xzl-ui/src/views/logistics3.vue

@@ -1,6 +1,6 @@
 <template>
   <div id="screen" >
-    <iframe frameborder="no"  src="http://10.70.192.123/webroot/decision/view/form?viewlet=大屏/物流实时配送签收情况.frm" width="100%" height="100%"></iframe>
+    <iframe frameborder="no"  src="http://10.70.192.123/webroot/decision/view/form?viewlet=大屏新版20240226/物流实时配送签收大屏.frm" width="100%" height="100%"></iframe>
   </div>
 </template>
 <script>

+ 1 - 1
xzl-ui/src/views/logistics4.vue

@@ -1,6 +1,6 @@
 <template>
   <div id="screen" >
-    <iframe frameborder="no"  src="http://10.70.192.123/webroot/decision/view/form?viewlet=大屏/营销看板.frm" width="100%" height="100%"></iframe>
+    <iframe frameborder="no"  src="http://10.70.192.123/webroot/decision/view/form?viewlet=大屏新版20240226/营销业务看板.frm" width="100%" height="100%"></iframe>
 
   </div>
 </template>