VueとVue-Element-Admin(4):vue-element-adminのユーザーログイン統合

Vue-element-adminは、mock.jsデータを使用してユーザーと権限を検証します。このフレームワークを使用して開発する最初のことは、ユーザーの統合と変換であり、ローカルテスト環境のユーザーとロールの情報を使用してログイン検証を完了します。

 1つは、vue-element-adminのログインロジックです。

/ views / loginディレクトリのindex.vueはログインインターフェイスであり、SocialSignin.vueはサードパーティのログインページであり、auth-redirect.vueは理解していませんでした。

login
| components
| | SocialSignin.vue
| auth-redirect.vue
| index.vue

 index.vueのログインボタンはhandleLogin()メソッドをトリガーします。このメソッドはstore / user.jsのログインアクションを呼び出して状態を変更し、login()アクションはapi / user / loginインターフェイスリクエストを呼び出してユーザーを取得します。バックエンドデータは次のとおりです。

// hadleLogin方法完成用户登录动作
handleLogin() {
      this.$refs.loginForm.validate(valid => {
        if (valid) {
          this.loading = true
          this.$store.dispatch('user/login', this.loginForm)
            .then(() => {
              this.$router.push({ path: this.redirect || '/', query: this.otherQuery })
              this.loading = false
            })
            .catch(() => {
              this.loading = false
            })
        } else {
          console.log('error submit!!')
          return false
        }
      })
    }

//store/user.js中login这个action的代码:

const actions = {
  // user login
  login({ commit }, userInfo) {
    const { username, password } = userInfo
    return new Promise((resolve, reject) => {
      login({ username: username.trim(), password: password }).then(response => {
        const { data } = response
        commit('SET_TOKEN', data.token)
        setToken(data.token)
        resolve()
      }).catch(error => {
        reject(error)
      })
    })
  }
}

// api/user/login的接口请求代码:

import request from '@/utils/request'

export function login(data) {
  return request({
    url: '/vue-element-admin/user/login',
    method: 'post',
    data
  })
}

mock / user.jsは、/ vue-element-admin / user / loginの残りのリクエストの戻りデータを定義し、adminとeditorの2つの役割とトークンのコンテンツを定義し、postリクエストの戻りを定義します(パラメータはloginname)は1つのR(コード+トークン情報)ネストされたjson、コードはリクエストの応答コード、トークンはユーザーの単一の状態情報(グローバルメンテナンス〜)、user / getInfoメソッドはユーザーとロールの情報を取得しますトークンパラメータ、およびバックエンドは対応するインターフェイスを提供する必要があります;


const tokens = {
  admin: {
    token: 'admin-token'
  },
  editor: {
    token: 'editor-token'
  }
}

const users = {
  'admin-token': {
    roles: ['admin'],
    introduction: 'I am a super administrator',
    avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
    name: 'Super Admin'
  },
  'editor-token': {
    roles: ['editor'],
    introduction: 'I am an editor',
    avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
    name: 'Normal Editor'
  }
}

module.exports = [
  // user login
  {
    url: '/vue-element-admin/user/login',
    type: 'post',
    response: config => {
      const { username } = config.body
      const token = tokens[username]

      // mock error
      if (!token) {
        return {
          code: 60204,
          message: 'Account and password are incorrect.'
        }
      }

      return {
        code: 20000,
        data: token
      }
    }
  }
]

 ロジックは終了していません。フロントエンドとバックエンドのルーティングロジックはフレームワークに記述されています。ログインページがリダイレクトとして定義されていることがわかります(http:// localhost:9527 /#/ login?redirect =%2Fdashboard) 、ルーティングジャンプが含まれるため、アクセス許可を確認する必要があります。グローバルナビゲーションガードを担当するグローバルpermission.jsがルートディレクトリにあります。これは、ログインに成功した後にホームページにアクセスする必要があることを定義します。Vuexが再度開始します。対応するユーザー(ロール)のアクセス許可を取得し、アクセス許可がある場合にのみジャンプし、アクセス許可なしでエラーを報告するuser / getInfoリクエスト。

import router from './router'
import store from './store'
import { Message } from 'element-ui'
import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css' // progress bar style
import { getToken } from '@/utils/auth' // get token from cookie
import getPageTitle from '@/utils/get-page-title'
NProgress.configure({ showSpinner: false }) // NProgress Configuration
const whiteList = ['/login', '/auth-redirect'] // no redirect whitelist

//导航守卫,在router之前,拿到to和from以及下一步动作next
router.beforeEach(async(to, from, next) => {
  // 开启进度条
  NProgress.start()
  // 拿title
  document.title = getPageTitle(to.meta.title)
  // 判断浏览器是否持有token
  const hasToken = getToken()
 // 有token若是登录页就放行,有token不是登录页就验证是否有权限,有就放行,没有的话在vuex中拿,vuex发起user/getinfo请求拿权限,并记住想要跳转到的页面,也就是redirect的位置;
  if (hasToken) {
    if (to.path === '/login') {
      next({ path: '/' })
      NProgress.done() // hack: https://github.com/PanJiaChen/vue-element-admin/pull/2939
    } else {
      const hasRoles = store.getters.roles && store.getters.roles.length > 0
      if (hasRoles) {
        next()
      } else {
        try {
          const {roles} = await store.dispatch('user/getInfo')
          const accessRoutes = await store.dispatch('permission/generateRoutes', roles)
          router.addRoutes(accessRoutes)
          next({...to, replace: true})
        } catch (error) {
          await store.dispatch('user/resetToken')
          Message.error(error || 'Has Error')
          next(`/login?redirect=${to.path}`)
          NProgress.done()
        }
      }
    }
  } else {
    //没有token的话白名单就放行,不是的话就跳到登录页记录要去的页面;
    if (whiteList.indexOf(to.path) !== -1) {
      // in the free login whitelist, go directly
      next()
    } else {
      next(`/login?redirect=${to.path}`)
      NProgress.done()
    }
  }
})

router.afterEach(() => {
  // 结束进度条
  NProgress.done()
})

したがって、一般的なプロセスを取得できます。フロントエンドはログインページでログイン要求を開始し、ルーティングガード中にvuexのトークン(ログイン要求のバックエンドからフロントエンドに返される暗号化された文字列)を取得して、ユーザーを開始します。 / infoは、バックエンド情報からユーザーの許可を取得するように再度要求します。トークン内の暗号化された情報にはすべてのユーザー情報が付随していますが、フロントエンドはそれを復号化しません。ログイン後、フロントエンドはバックエンドからデータを要求するたびにトークンヘッダーが作成され、バックエンドはトークンヘッダーに従って検証し、それに応じてフロントエンドに戻ります。情報; 

2.バックエンド変換-ユーザークエリインターフェイス

ローカルテストは最初にクロスドメインの問題を解決します:統合におけるCORS(クロスドメインリソースシェアリング)の問題

フロントエンドのデバッグの問題もあります:Vscodeを使用してChromeでvueをデバッグします

モデル、マッパー、サービス、Impi、フィルター(リクエストフィルター)、coモジュールを完了する必要があります

バックエンドが完了した後のGitアドレス:バックエンドコード

1.テーブルとモデル

基本ユーザーログインモジュールには、少なくとも4つのテーブルが必要です。ユーザーテーブル(基本ユーザー情報)、ロールテーブル(ユーザー特権情報)、認証テーブル(ユーザーパスワードとトークン情報)、ユーザーとロールのディメンションテーブルで、h2データベースに組み込まれています。アイデア3の対応する表については、前のブログ投稿を参照してください。springbootに基づくWeb開発構成、springboot、mybatisPlus、h2、lambok、swaggerシリーズ構成を使用して、バックエンドインターフェイスの開発を完了します。

// user表
create table LT_SYS_USER
(
	USER_ID VARCHAR(20) not null
		primary key,
	USER_CODE VARCHAR(64),
	USER_NAME VARCHAR(100) not null,
	USER_NAME_EN VARCHAR(100),
	ORGAN_ID VARCHAR(20),
	ORGAN_CODE VARCHAR(64),
	ORGAN_NAME VARCHAR(100),
	EMAIL TEXT,
	MOBILE VARCHAR(100),
	PHONE VARCHAR(100),
	SEX INT,
	AVATAR TEXT,
	SIGN VARCHAR(200),
	USER_SORT INT,
	STATUS INT not null,
	CREATE_BY VARCHAR(64),
	CREATE_DATE DATETIME,
	UPDATE_BY VARCHAR(64),
	UPDATE_DATE DATETIME,
	REMARKS TEXT,
	DELETED VARCHAR(5) not null,
	CORP_ID VARCHAR(64),
	REG_DATE DATETIME
);
// role角色表
create table LT_SYS_ROLE
(
	ROLE_ID VARCHAR(20) not null
		primary key,
	ROLE_CODE VARCHAR(64) not null,
	ROLE_NAME VARCHAR(100) not null,
	ROLE_TYPE VARCHAR(100),
	ROLE_SORT INT,
	DATA_SCOPE INT,
	BIZ_SCOPE VARCHAR(255),
	DELETED TINYINT not null,
	CREATE_BY VARCHAR(64) not null,
	CREATE_DATE DATETIME not null,
	UPDATE_BY VARCHAR(64),
	UPDATE_DATE DATETIME,
	REMARKS TEXT,
	CORP_ID VARCHAR(64)
);

//auth权限表
create table LT_SYS_USER_AUTH
(
	AUTH_ID VARCHAR(20) not null
		primary key,
	USER_ID VARCHAR(20) not null,
	LOGIN_NAME VARCHAR(100) not null,
	PASSWD VARCHAR(100) not null,
	TOKEN TEXT,
	EXPIRE_TIME DATETIME,
	DINGTALK_OPENID LONGTEXT,
	WELINK_OPENID VARCHAR(100),
	WX_OPENID VARCHAR(100),
	MOBILE_IMEI VARCHAR(100),
	USER_TYPE VARCHAR(16),
	PWD_SECURITY_LEVEL INT,
	PWD_UPDATE_DATE DATETIME,
	PWD_UPDATE_RECORD TEXT,
	PWD_QUEST_UPDATE_DATE DATETIME,
	LAST_LOGIN_IP VARCHAR(100),
	LAST_LOGIN_DATE DATETIME,
	FREEZE_DATE DATETIME,
	FREEZE_CAUSE VARCHAR(200),
	USER_WEIGHT INT,
	CREATE_BY VARCHAR(64),
	CREATE_DATE DATETIME,
	UPDATE_BY VARCHAR(64),
	UPDATE_DATE DATETIME,
	REMARKS TEXT,
	DELETED INT default 0,
	STATUS VARCHAR(100)
);

// USER和role维表
create table LT_SYS_USER_ROLE
(
	USER_ID VARCHAR(20) not null,
	ROLE_ID VARCHAR(20) not null,
	primary key (USER_ID, ROLE_ID)
);

comment on column LT_SYS_USER_ROLE.USER_ID is '用户id';
comment on column LT_SYS_USER_ROLE.ROLE_ID is '角色id';

2、マッパー

テストデータを3つのテーブルに挿入した後、バックエンドのrestAPIクエリインターフェイスの記述を開始します。まず、model /ディレクトリの下にuserModelフォルダーを作成し、3つの新しいクラスUser.java、Role.java、およびAuth.javaを作成します。 model / userModelでlambokとswaggerを使用します。注釈は、コンストラクターとインターフェースの使用に便利です。次に、mapper / userMapperディレクトリーに対応するインターフェースUserMapperとUserAuthMapperを作成し、mybatisplus継承ベースマッパーを使用していくつかのクエリをカスタマイズします。

// usermapper接口
package com.example.testspring.mapper.userMapper;
import com.example.testspring.model.userModel.Role;
import com.example.testspring.model.userModel.User;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.testspring.model.userModel.UserRole;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;
@Mapper
public interface UserMapper extends BaseMapper<User>{
    User getByIdLazy(String userId);
    User findById(String userId);
    boolean deleteRoleByUserId(String userId);
    boolean insertRolesBatch(@Param("list") List<UserRole> list);}

//RoleMapper 接口
package com.example.testspring.mapper.userMapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.testspring.model.userModel.Role;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;
@Mapper
public interface RoleMapper extends BaseMapper<Role> {
    List<Role> getListByUserId(String userId);
    Role findById(String roleId);
}

//AuthMapper
package com.example.testspring.mapper.userMapper;
import com.example.testspring.model.userModel.Auth;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface AuthMapper extends  BaseMapper<Auth>{
   // Auth getByLoginName(String loginName);
}

 3.サービスとImpi

次に、対応するインスタンス化クラスUserServiceおよびAuthServiceをservice / userServiceディレクトリに作成します。Impiはクラス実装であり、記述できません。

// UserService 
package com.example.testspring.service.userService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.testspring.mapper.userMapper.RoleMapper;
import com.example.testspring.mapper.userMapper.UserMapper;
import com.example.testspring.model.userModel.Role;
import com.example.testspring.model.userModel.User;
import com.example.testspring.model.userModel.UserRole;
import com.example.testspring.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    @Autowired
    private UserMapper UserMapper;
    @Autowired
    private RoleMapper RoleMapper;
    @Override
    public User getUserAndRolesById(String userId) {
        User ltSysUser = UserMapper.findById(userId);
        List<Role> roles = RoleMapper.getListByUserId(userId);
        ltSysUser.setRoles(roles);
        return ltSysUser;
    }

    @Override
    public boolean saveRoleIdsByUserId(String userId, List<String> roleIds) {
        UserMapper.deleteRoleByUserId(userId);
        List<UserRole> list = roleIds.stream().map(roleId -> {
            return new UserRole(roleId, userId);
        }).collect(Collectors.toList());
        return UserMapper.insertRolesBatch(list);
    }
}

//RoseService 
package com.example.testspring.service.userService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.testspring.mapper.userMapper.RoleMapper;
import com.example.testspring.model.userModel.Role;
import com.example.testspring.service.RoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
@Service

public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements RoleService {
    @Autowired
    private RoleMapper roleMapper;
    @Override
    public List<Role> getListByUserId(String userId) {
        return roleMapper.getListByUserId(userId);
    }

}
//AuthService AuthService 类
package com.example.testspring.service.userService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.testspring.mapper.userMapper.AuthMapper;
import com.example.testspring.model.userModel.Auth;
import com.example.testspring.service.AuthService;
import com.example.testspring.utils.SecurityUtil;
import org.springframework.stereotype.Service;

import java.util.List;
@Service
public class AuthServiceImpl extends ServiceImpl <AuthMapper, Auth> implements AuthService {
    @Override
    public Auth getByLoginName(String loginName) {
        LambdaQueryWrapper<Auth> queryWrapper = Wrappers.<Auth>lambdaQuery()
                .eq(Auth::getLoginName, loginName);
        List<Auth> ltSysUserAuthList = this.baseMapper.selectList(queryWrapper);
        if (CollectionUtils.isEmpty(ltSysUserAuthList)) {
            return null;
        }
        else if (ltSysUserAuthList.size() > 1) {
            log.error("用户账号下包含多个用户信息,请检查数据!");
        }
        return ltSysUserAuthList.get(0);
    }

    @Override
    public boolean updatePwdByUserId(String userId, String newPwd) {

        String entryptPassword = SecurityUtil.entryptPassword(newPwd);
        LambdaUpdateWrapper<Auth> wrapper = Wrappers.<Auth>lambdaUpdate()
                .eq(Auth::getUserId, userId).set(Auth::getPasswd, entryptPassword);
        return this.baseMapper.update(null, wrapper) > 0;
    }

}

次に、resoureces / mybatis /の下に対応するsqlquery xmlファイルを作成し、マッパーファイルと1対1で対応します。githubを参照できます。

4、コントローラー

最後に、これはコントローラーのrestAPIです。フロントエンドは/ user / loginのPOSTリクエストです(パラメーターはloginnameとpasswdであり、戻り値はネストされたjson(code + tokens)です)。したがって、対応するAPIはローカルで構築され、user / infoは、getリクエストによって返されるユーザー情報です。例としてログインを取り上げます。ここでのコントローラーは、リクエストのインターセプトを完了するためにtokenfilterを必要とします。

    @PostMapping("/login")
    @ApiImplicitParam(name = "req", value = "用户登陆信息", dataType = "LoginReq")
    public R login(@RequestBody @NotNull LoginReq req) throws Exception {
        String loginName = req.getLoginName();
        String passwd = req.getPasswd();
        Auth info = authService.getByLoginName(loginName);
        // 验证用户状态
        LambdaQueryWrapper<User> wrapper = Wrappers.<User>lambdaQuery().eq(User::getUserId, info.getUserId()).eq(User::getStatus, 0);
        User user = userService.getOne(wrapper);
        Assert.notNull(user, "用户已禁用, 请联系管理员");

        if (SecurityUtil.validatePassword(passwd, info.getPasswd())) {
            // 封装 Token
            String jwtToken = generateByUserInfo(info);
            return R.ok(jwtToken);
        }
        else {
            throw new LightUapException(LightErrorCode.FORBIDDEN);
        }
    }

    private String generateByUserInfo(Auth auth) throws Exception {
        String userId = auth.getUserId();
        User user = userService.getUserAndRolesById(userId);
        if (user == null) {
            throw new LightUapException("用户信息不存在,请联系管理员");
        }
        // 查询用户账户信息
        LightUserEntity userEntity = BeanConverter.convert(user, LightUserEntity.class);
        userEntity.setLoginName(auth.getLoginName());
        /**
         * 返回前台时,只返回当前用户的角色
         */
        List<Role> roles = user.getRoles();
        if (!CollectionUtils.isEmpty(roles) && roles.size() >= 1) {
            List<LightRoleEntity> collect = roles.stream().map(r -> r.setUsers(null)).map(r -> LightRoleEntity.builder()
                    .roleId(r.getRoleId()).roleCode(r.getRoleCode()).roleName(r.getRoleName()).roleType(r.getRoleType())
                    .build()).collect(Collectors.toList());
            userEntity.setRoles(collect);
            // 默认一个个角色为主要角色
            // userEntity.setRole(collect.get(0));
        }
        // 生成 token
        // 去除用户信息中的头像防止token过大
        userEntity.setAvatar("");
        String jwtToken = LightTokenUtil.createJwtDefaultExp(userEntity);
        return jwtToken;
    }

5.フィルターインターセプター 

CORSの設定とリクエストのインターセプトを含めて、構成クラスとして記述できます。ここでのtokenInceptorは、事前チェックのフライトとリクエストの分類を完了し、ログインリクエストが解放され、他のリクエストは最初にヘッダーによって取得され、次にトークンはAPIに入る前に均一に解析されます。コントローラーロジック。

package com.example.testspring.utils;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.example.testspring.model.userModel.LightUserEntity;
import com.example.testspring.req.LightException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.cors.CorsUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import org.springframework.web.util.WebUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.util.Base64;
import java.util.Enumeration;

public class TokenInterceptor extends HandlerInterceptorAdapter {

    /**
     * 根据请求不同对token进行处理
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)  throws Exception {
        // 预检请求,预检飞行
        if(CorsUtils.isCorsRequest(request) && "OPTIONS".equals(request.getMethod())){
            response.setCharacterEncoding( "UTF-8");
            response.setContentType( "application/json; charset=utf-8");
            response.setStatus(200);
            response.setHeader("Access-Control-Allow-Credentials","true");
            response.setHeader("Access-Control-Allow-Origin","http://localhost:9527");
            response.setHeader("Access-Control-Allow-Headers","x-requested-with, authorization, Content-Type, Authorization, credential, X-XSRF-TOKEN,token,username,client");
            response.setHeader("Access-Control-Expose-Headers","x-requested-with, authorization, Content-Type, Authorization, credential, X-XSRF-TOKEN,token,username,client");
            PrintWriter out = null ;
            try{
                JSONObject res = new JSONObject();
                res.put( "200", "sucess");
                out = response.getWriter();
                out.append(res.toString());
                return false;
            }
            catch (Exception e){
                e.printStackTrace();
                response.sendError( 500);
                return false;
            }
        }
        String accessToken = request.getHeader("Authorization");
        //System.out.println(request.getHeader("Authorization"));
        //String str=request.getParameter("Authorization");
        if (StringUtils.isNotBlank(accessToken)) {
            LightUserEntity subject = LightTokenUtil.getSubject(accessToken);
            request.getSession().setAttribute("USER_INFO", subject);
            return true;
        }
        throw new LightException("TOKEN不合法,访问拒绝");
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
                           ModelAndView modelAndView) throws Exception {
        super.postHandle(request, response, handler, modelAndView);
    }
}

最後に、postmanを使用してログインインターフェイスと情報インターフェイスをテストします。

第三に、ユーザーログイン統合のフロントエンド変換

デフォルトはモックデータであるため、最初に.env.developmentのBASE_APIアドレスをローカルバックエンドアドレスに変更します。

VUE_APP_BASE_API = 'http://localhost:9090'

 ログイン統合のテストが完了しました。

おすすめ

転載: blog.csdn.net/yezonggang/article/details/109850163