Shiro前后端分离流程

news2024/11/27 22:46:33

1.自定义filter

拦截所有携带token的请求, 调用自定义realm,判断token是否正确,不正确realm抛出异常,在filter中被捕获,重定向至token不正确界面

重写了三个方法:

1》isAccessAllowed:如果带有 token,则对 token 进行检查,否则可能是执行登陆操作或者是游客状态访问,无需检查 token,直接返回 true(放行)

 2》isLoginAttempt:被isAccessAllowed调用,检测 header 里面是否包含 Token 字段。

3》executeLogin:实际的登录方法,当有token时,被isaccessallwed调用,执行subject.login方法,

即调用自定义realm中的doGetAuthenticationInfo方法,如果登录成功,isaccessallwed就返回true,如果登录失败,realm中抛出异常,抛到subject.login,被try  catch捕获,重定向到无权限界面(因为filer父类BasicHttpAuthenticationFilter没有抛出异常,所以这里不能抛出,也就不能给springboot的异常处理器处理)

4》prehandle  处理跨域

package com.hxut.rj1192.dormitory2.filter;


import com.hxut.rj1192.dormitory2.pojo.JWTToken;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;

//       所有的请求都会到达这个过滤器处理。
//        我们需要重写几个方法:
//
//        isAccessAllowed:是否允许访问。如果带有 token,则对 token 进行检查,否则可能是执行登陆操作或者是游客状态访问,无需检查 token,直接返回 true

//        isLoginAttempt:检测 header 里面是否包含 Token 字段。

//        executeLogin:实际的登录方法

//        preHandle:拦截器的前置拦截,因为我们是前后端分析项目,项目中除了需要跨域全局配置之外,我们再拦截器中也需要提供跨域支持。这样,拦截器才不会在进入Controller之前就被限制了。

public class JWTFilter extends BasicHttpAuthenticationFilter {

    //是否允许访问,如果带有 token,则对 token 进行检查 (true即允许通过,false不通过),否则直接通过
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        System.out.println("JWTFilter的isAccessAllowed方法  登录验证 调用isLoginAttempt方法判断是否有jwt 没有就放行有就验证jwt返回true 否则返回false");
        //判断请求的请求头是否带上 token
        if (isLoginAttempt(request, response)) {
            //如果存在,则进入 executeLogin 方法执行登入,检查 token 是否正确
            try {
                //  因为父类没有抛出异常,所以子类也不能抛出异常
                executeLogin(request, response);
                return true;
            } catch (AuthenticationException e) {
                //token 错误
                responseError(response, e.getMessage());
            }
        }
        //如果请求头不存在 Token,则可能是执行登录操作或者是游客状态访问,无需检查 token,直接返回 true
        return true;
    }

    /**
     * 判断用户是否想要登入。
     * 检测 header 里面是否包含 Token 字段
     */

    @Override
    protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
        HttpServletRequest req = (HttpServletRequest) request;
        String token = req.getHeader("Authorization");
        System.out.println("JWTFilter的isLoginAttempt方法  判断请求头中有没有jwt" + (token == null));
        return token != null;
    }

    /*
     * 实际的登录方法,这里我们重写了这个方法
     * 当登录失败时,realm 返回false login 会抛出异常,被上面调用executeLogin isAccessAllowed 方法捕获
     * */
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) {
        System.out.println("JWTFilter的executeLogin方法  验证token");
        HttpServletRequest req = (HttpServletRequest) request;
        String token = req.getHeader("Authorization");
        System.out.println("请求头中的token    " + token);
        JWTToken jwt = new JWTToken(token);
        //交给自定义的realm对象去登录,如果错误他会抛出异常并被捕获
        getSubject(request, response).login(jwt);
        return true;
    }

    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse res = (HttpServletResponse) response;
        res.setHeader("Access-control-Allow-Origin", req.getHeader("Origin"));
        res.setHeader("Access-control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
        res.setHeader("Access-control-Allow-Headers", req.getHeader("Access-Control-Request-Headers"));
        // 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态
        if (req.getMethod().equals(RequestMethod.OPTIONS.name())) {
            res.setStatus(HttpStatus.OK.value());
            return false;
        }
        return super.preHandle(request, response);
    }

    /**
     * 将非法请求跳转到 /unauthorized/**
     */
    private void responseError(ServletResponse response, String message) {
        System.out.println("跳转到错误页面");
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        //设置编码,否则中文字符在重定向时会变为空字符串
        try {
            message = URLEncoder.encode(message, "UTF-8");
            httpServletResponse.sendRedirect("/unauthorized/" + message);
        } catch (IOException e) {
        }
    }
}

2.自定义AuthenticationToken  类

     调用subject.logn方法 ,需要一个 AuthenticationToken  类,来存储用户信息,不设置就是默认的,有用户名密码等,但是我要验证的是jwt,不是用户名和密码,所以要写个新的AuthenticationToken类,来作为subject.login方法的参数,传递给realm中doGetAuthenticationInfo

package com.hxut.rj1192.dormitory2.pojo;

import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;

/*
    JwtToken代替原生的UsernamePasswordToken ,所以要继承AuthenticationToken类
 */
public class JWTToken implements AuthenticationToken {
    private String jwt;

    public JWTToken(String jwt) {
        this.jwt = jwt;
    }

    //获取用户名 ,这里直接返回token
    @Override
    public Object getPrincipal() {
        return jwt;
    }

    //返回密码,这里直接返回token
    @Override
    public Object getCredentials() {
        return jwt;
    }
}

3.自定义realm  和不分离没有区别

package com.hxut.rj1192.dormitory2.realm;


import com.hxut.rj1192.dormitory2.mapper.UserMapper;
import com.hxut.rj1192.dormitory2.pojo.JWTToken;
import com.hxut.rj1192.dormitory2.pojo.User;
import com.hxut.rj1192.dormitory2.service.impl.CheckloginService;
import com.hxut.rj1192.dormitory2.util.JWTUtil;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Set;

@Component
public class AccountRealm extends AuthorizingRealm {
    //根据token判断此Authenticator是否使用该realm
    //必须重写不然shiro会报错
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JWTToken;
    }

    /**
     * 只有当需要检测用户权限的时候才会调用此方法,例如@RequiresRoles,@RequiresPermissions之类的
     */
    @Autowired
    UserMapper userMapper;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("自定义realm的doGetAuthorizationInfo执行了~~~~~查询用户权限");
        String token = principals.toString();
        String username = JWTUtil.getUsername(token);
        System.out.println("自定义realm的doGetAuthorizationInfo执行了~~~~~查询用户权限,查到的用户名" + username);
        System.out.println("自定义realm的doGetAuthorizationInfo执行了~~~~~查询用户权限,查到的角色名" + userMapper.queryrole(username));
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        //查询数据库获取用户的角色
        info.addRole(userMapper.queryrole(username));
        //查询数据库获取用户的权限
        Set<String> querypermission = userMapper.querypermission(username);
        for (String temp : querypermission) {
            info.addStringPermission(temp);
        }
        return info;
    }


    /**
     * 默认使用此方法进行用户名正确与否验证,错误抛出异常即可,在需要用户认证和鉴权的时候才会调用
     */
    @Autowired
    CheckloginService checkloginService;

    // 获取token中的用户名,去数据库查询相应信息,如果为空,代表没有该用户
    // 验证token是否正确
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        String jwt = (String) token.getCredentials();
        //decode时候出错,可能是token的长度和规定好的不一样了
        String username = JWTUtil.getUsername(jwt);
        System.out.println("accountrealm的 doGetAuthenticationInfo方法(验证token)中获取到的用户名" + username);
        JWTUtil.verify(jwt);//如果错误,会抛一个 过期错误,或者一个token过期错误
        User user = userMapper.querybyname(username);
        if (user == null) {
            throw new AuthenticationException("该用户不存在");
        }
//        前后端不分离,返回的是用户名,密码 ,realm名
        return new SimpleAuthenticationInfo(jwt, jwt, "AccountRealm");
    }
}


4.shiroconfig

1》设置自定义的realm

2》设置关闭session

3.》开启shiro注解

4》设置拦截,拦截除了unauthorized/的所有路径,交给自定义filter去判断是否放行(/unauthorized/是没有权限时重定向的路径,如果没有放行,则带有token,被拦截,验证realm,抛出异常,跳转至/unauthorized重定向界面,被拦截,验证realm.....无限循环了)

package com.hxut.rj1192.dormitory2.config;


import com.hxut.rj1192.dormitory2.filter.JWTFilter;
import com.hxut.rj1192.dormitory2.realm.AccountRealm;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.filter.mgt.DefaultFilterChainManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

@Configuration
public class ShiroConfig {
    @Bean(name = "securityManager")
    public DefaultWebSecurityManager securityManager(AccountRealm accountRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 设置自定义 realm
        securityManager.setRealm(accountRealm);

        //关闭session
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator sessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        sessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(sessionStorageEvaluator);
        securityManager.setSubjectDAO(subjectDAO);
        return securityManager;
    }

    /**
     * 先走 filter ,filter 如果检测到请求头存在 token,则用 token 去 login,走 Realm 去验证
     */
    @Bean("shiroFilterFactoryBean")
    public ShiroFilterFactoryBean factory(DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        Map<String, Filter> filterMap = new LinkedHashMap<>();
        //设置自己的过滤器并且取名为jwt
        filterMap.put("jwt", new JWTFilter());
        shiroFilterFactoryBean.setFilters(filterMap);

        // 设置无权限时跳转的 url;
        shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");
        Map<String, String> filterRuleMap = new HashMap<>();
        // 所有请求通过我们自己的JWT Filter
        filterRuleMap.put("/**", "jwt");
        // 除了无权限时跳转的路径
        filterRuleMap.put("/unauthorized/**", "anon");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterRuleMap);
        return shiroFilterFactoryBean;
    }

    /**
     * 添加注解支持,如果不加的话很有可能注解失效
     */
    @Bean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
        return defaultAdvisorAutoProxyCreator;
    }
/**
     * 添加注解支持,如果不加的话很有可能注解失效
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }

    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }
}

5.jwtutil 

package com.hxut.rj1192.dormitory2.util;

import com.alibaba.fastjson.JSON;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.hxut.rj1192.dormitory2.pojo.ReturnMap;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.shiro.authc.AuthenticationException;
import org.springframework.context.annotation.Configuration;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class JWTUtil {
    //设置过期时间  一分钟过期
    private static final long EXPIRE_DATE = 60 * 1000 * 10;
    // token 是给前端的一段有我自己标识(TOKEN_SECRET 标识这个toekn确实是服务器生成的 )的密码,
    // 因为前后端分离的项目没有session cookie,就需要用token来标识用户,替代原来的session 和cookie
    // 如果其他人想要伪造token,则必须要知道我的标识 即TOKEN_SECRET,所以别人不能伪造
//    如果更安全一点,可以使用md5对普通生成的toekn二次加密加盐,即使黑客得到了toekn,
//    它也不能破解出 toekn第二部分携带的信息(但是可以伪造用户去请求服务器(toekn就是密码,密码泄露了,肯定能登录的),所以toekn要设置有效期)
    private static final String TOKEN_SECRET = "ZYKZYKZYKZYK";

    public static String createToken(String username, String password) {
        String token = "";
        try {
            //过期时间
            Date date = new Date(System.currentTimeMillis() + EXPIRE_DATE);
            //秘钥及加密算法
            Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
            //设置头部信息
            Map<String, Object> header = new HashMap<>();
            header.put("typ", "JWT");
            header.put("alg", "HS256");
            //携带username,password信息,生成签名
            token = JWT.create()
                    .withHeader(header)
                    .withClaim("username", username)
                    .withExpiresAt(date)
                    .sign(algorithm);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        return token;
    }

    //无需解密也可以获取token的信息
    public static String getUsername(String token) {
        try {
            DecodedJWT jwt = JWT.decode(token);
            return jwt.getClaim("username").asString();
        } catch (JWTDecodeException e) {
            return null;
        }

    }

    public static boolean verify(String token) {
        /**
         * @desc 验证token,通过返回true
         * @params [token]需要校验的串
         **/
        try {
            DecodedJWT jwt2 = JWT.decode(token);
            Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
            JWTVerifier verifier = JWT.require(algorithm).build();
            DecodedJWT jwt = verifier.verify(token);
            return true;
        } catch (TokenExpiredException e) {
            throw new AuthenticationException(JSON.toJSONString( ReturnMap.fail("token过期,登录失败","-11")));
        }
        catch(SignatureVerificationException e){
            throw new AuthenticationException(JSON.toJSONString( ReturnMap.fail("token可能被篡改,或token错误,登录失败","-11")));
        }
    }
}

 

 有点问题:每次带token的请求都会请求一次mysql,最好先查redis,没有再查mysql

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/26989.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

有一个项目管理软件,名字叫8Manage PM!

优秀的软件工具在项目管理中起到极为重要的作用。8Manage PM项目管理软件由高亚科技自主研发&#xff0c;为项目工作提供项目功能、业务功能、服务功能和工具&#xff0c;有力推动项目成功。 8Manage软件项目功能包括完整性管理、需求管理、计划和执行、资源管理、工作量&…

锐捷BGP基础配置

目录 ​编辑 配置IBGP邻居 配置EBGP邻居 BGP其它配置 配置IBGP邻居 R2、R3、R4底层IGP互通&#xff0c;此处IGP互通配置不做介绍 R2与R4通过Loop0建立IBGP邻居&#xff0c;R3与R4通过Loop0建立IBGP邻居 R4充当反射器&#xff0c;R2和R3作为客户端&#xff08;通过反射可以将…

Vue中设置背景图片和透明度

如果文章对你有帮助欢迎【关注❤️❤️❤️点赞&#x1f44d;&#x1f44d;&#x1f44d;收藏⭐⭐⭐】一键三连&#xff01;一起努力&#xff01; 今天来为我自己的项目设置一个好看的登录页面之前是这样的&#xff1a; 乍一看感觉还行&#xff0c;越看越难受&#xff0c;弄一…

Nodejs http模块常用方法

视频链接&#xff1a;黑马程序员Node.js全套入门教程 文章目录http模块1 什么是http模块2 进一步理解http的作用3 服务器相关的概念3.1 IP地址3.2 域名和域名服务器3.3 端口号4 创建简单的web服务器1 步骤2 代码实现3 req请求对象4 res响应对象5 解决中文乱码问题5 简单路由效果…

《Java并发编程之美》读书笔记——ThreadLocalRandom类原理剖析

文章目录1.Random类的局限性2.ThreadLocalRandom3.源码分析Unsafe机制current()方法int nextInt(int bound)方法1.Random类的局限性 在JDK之前包括现在&#xff0c;java.util.Random都是使用比较广泛的随机数生成工具类。 下面我们先来看一下Random的使用方法。 // 创建一个…

kubelet源码 删除pod(一)

k8s版本为1.25.3版本 kubectl delete pod name当删除一个pod的时候会经历一下流程 kubectl会发pod消息给api server。apiserver将信息存入etcd&#xff0c;然后返回确认信息。apiserver开始反馈etcd中pod对象的变化&#xff0c;其他组件使用watch机制跟踪apiserver上的变动。…

行业生态重塑中,新氧如何逆风翻盘

美东时间11月18日盘前&#xff0c;中国互联网医美第一股新氧科技发布2022财年第三季度的业绩报告&#xff0c;业绩符合其业绩指引。 据新氧该季度财报显示&#xff0c;第三季度实现非美国通用会计准则归属于新氧科技的净利润990万元人民币&#xff08;140万美元&#xff09;。…

表格分组标签:表格行分组中的隐藏功能

在程序员的认知中&#xff0c;表格中存在行分组标签&#xff0c;也就是thead&#xff0c;tbody&#xff0c;tfoot三个行分组标签。也许你会认为我在这里还是为大家继续讲解thead&#xff0c;tbody&#xff0c;tfoot三个标签&#xff0c;那就大错特错了 今天除了要讲解他的基础作…

栈和队列

声明&#xff1a;本文主要作为作者的复习笔记&#xff0c;由于作者水平有限&#xff0c;难免有错误和不准确之处&#xff0c;欢迎读者批评指正。 目录快捷跳转线性表接口两个常用子类什么时候选择ArrayList&#xff0c;什么时候选择LinkedList?栈和队列的关系栈栈的实现根据使…

ASP.NET Core教程-Exception(异常和错误处理)

更新记录 转载请注明出处&#xff1a; 2022年11月22日 发布。 2022年11月20日 从笔记迁移到博客。 错误处理基础 错误处理说明 ASP.NET Core中的错误处理分为&#xff1a; ​ 局部Controller中处理错误 ​ 在Controller中定义错误代码和转到错误界面即可 ​ 全局应用中设置错误…

vue.js毕业设计,基于vue.js前后端分离教室预约系统设计与实现(H5移动项目)

功能模块 【后台管理功能模块】 系统设置&#xff1a;设置关于我们、联系我们、加入我们、法律声明 广告管理&#xff1a;设置小程序首页轮播图广告和链接 留言列表&#xff1a;所有用户留言信息列表&#xff0c;支持删除 会员列表&#xff1a;查看所有注册会员信息&#xff0c…

[附源码]计算机毕业设计JAVA家政管理系统

[附源码]计算机毕业设计JAVA家政管理系统 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis M…

Spring Boot中Node.js的下载与Vue CLI在IDEA中的部署及使用(图文解释 简单易懂)

仍有问题可点赞关注收藏后在评论区留言~~~ 一、Node.js与npm的下载 在使用Vue CLI(Vue脚手架)搭建前端系统的时候&#xff0c;因为需要用到npm安装Vue CLI&#xff0c;而npm是集成在Node.js中的&#xff0c;所以需要首先安装Node.js Node.js官网 下载过程很简单&#xff0c;…

数据库错误知识集2

Oracle数据库中最常见的索引类型是b-tree索引&#xff0c;也就是B-树索引&#xff0c;以其同名的计算科学结构命名。 union与union all的区别&#xff08;摘&#xff09;&#xff1a; ①对重复结果的处理&#xff1a;union会去掉重复记录&#xff0c;union all不会&#xff1b;…

转铁蛋白偶联糖(单糖/多糖),(Transferrin)TF-PEG-Dextran葡聚糖/Lysozyme溶菌酶

产品名称&#xff1a;转铁蛋白-聚乙二醇-葡聚糖 英文名称&#xff1a;TF-PEG-Dextran 纯度&#xff1a;95% 存储条件&#xff1a;-20C&#xff0c;避光&#xff0c;避湿 外观:固体或粘性液体&#xff0c;取决于分子量 PEG分子量可选&#xff1a;350、550、750、1k、2k、34k、5k…

校招补一个什么样的项目比较好?

校招一年比一年卷&#xff0c;千军万马过独木桥的情况下该怎样充实自己的项目经历&#xff1f;有两件事要搞明白&#xff01; 一、什么是【好】项目&#xff1f; 好项目在简历上要能一眼看出亮点和提问点。并且要能够把提问点对应的回答准备到位&#xff0c;这样才能在招聘量…

外汇天眼周回顾:Equiti开设最新办事处,Vantage推出Vantage Connect服务

在过去的一周内&#xff0c;国外外汇市场都发生了哪些引人注意的外汇新闻&#xff1f;天眼君带大家一起看看&#xff0c;具体新闻如下&#xff1a; 1、Equiti在塞浦路斯利马索尔开设最新办事处 据悉&#xff0c;多资产经纪商Equiti Group在宣布其在塞浦路斯的新业务获得CySEC …

CRM客户管理系统在市面上这么多?应该如何选型?各行业选型CRM必看!

当您下定决心怎样为您的民营企业选择合适的 CRM 时&#xff0c;须要考量很多不利因素。许多基本上国际标准适用于绝大多数寻求 CRM 软件系统的民营企业。其他注意事宜取决于您的业务体量和性质。下列是任何人 CRM 软件系统中须要注意的 14 项常规事宜&#xff0c;以及许多可能对…

【笑小枫玩转SpringBoot系列】目录,一篇拥有一个系列,值得收藏哟~

本系列简介 本系列主要讲解了JAVA后端开发中常用的操作&#xff0c;以初创一个SpringBoot项目开始&#xff0c;以实例的形式讲解了一个单项目框架的诞生。本文可以做为SpringBoot项目的入门学习&#xff0c;也可以当做一个初建项目的框架。 本文主要使用mysql数据库&#xff0…

Flutter for App——一个简单的BMI计算APP

一个简单的BMI计算APP效果截图初始化布局顶部区域标题计算结果组合顶部区域背景中间区域输入框输入行计算按钮分界线组合中间区域底部区域页面组合BMI计算Toast弹窗效果导入依赖封装效果截图 初始化 初始化表单控制器和焦点节点 void initView(){formKey GlobalKey<FormS…