热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

html简单登录界面代码_简单代码实现JWT(jsonwebtoken)完成SSO单点登录

本文作者:加耀投稿来源:https:zhuanlan.zhihu.comp64377462?utm_sourcewechat_session&utm_m

本文作者:加耀              

投稿来源:

https://zhuanlan.zhihu.com/p/64377462?utm_source=wechat_session&utm_medium=social&utm_oi=775841244587773952

使用JWT完成SSO单点登录

前两个月在公司面试过程中,发现很多求职者在简历中都写有实现过单点登录,并且使用的技术种类繁多,刚好公司项目中实现单点登录的是使用一款叫做JWT(json web token)的框架,其实现原理也挺简单的,遂想,是否自己可以用简单代码实现一个简易版的JWT来完成单点登录认证(SSO),所谓SSO单点登录,其实是指的一类解决方案,有很多种方式都可以实现,这里描述的JWT就是其中一种;

首先,我们先来JWT官方看一下JWT的简单介绍吧;

JWT的官网地址是:https://jwt.io/,我们在JWT的官网可以看到一个完整的JWT是由三个部分组成,分别是Header头部、Payload数据部分、Signature签名三部分组成;如图:

c4d5053450f7cab8f72d28eb03da6d87.png
img

如果将上图进行简化,JWT数据结构大抵如下

// Header
{
"alg": "HS256",
"typ": "JWT"
}

// Payload
{
// reserved claims
"sub": "1234567890",
"name": "John Doe",
"iat": "1516239022"
}

// $Signature
HS256(Base64(Header) + "." + Base64(Payload), "自定义密钥kyey" )

// JWT
JWT = Base64(Header) + "." + Base64(Payload) + "." + $Signature

为了更便捷的看懂JWT的生成和认证流程,这里给画了一张简略图供参考

3d4374a208f75fd9279320d2ea41d36d.png
img

如上图所示,根据指定的加密算法和密钥对数据信息加密得到一个签名,然后将算法、数据、签名一并使用Base64加密得到一个JWT字符串;而认证流程则是对JWT密文进行Base64解密后使用相同的算法对数据再次签名,然后将两次签名进行比较,判断数据是否有被篡改;

在整体流程上,算是比较简单了;再理解JWT的生成和认证原理后,我们就可以着手开始写代码了,我们可以使用一些其它的方式来完成类似的功能,从而实现JWT类似的效果;

首先,我们创建一个SpringBoot工程(方便调试不用自己写请求映射),创建好工程后,首先我们需要配置JWT的相关信息,比如:加密方式(当做是Header部分)、数据信息及token有效时间、JWT生成和认证算法等

在这里,我们先定义一个枚举FailureTime,用来定义支持的过期时间策略

public enum FailureTime {
    /**
     * 秒
     */
    SECOND,
    /**
     * 分
     */
    MINUTE,
    /**
     * 时
     */
    HOUR,
    /**
     * 天
     */
    DAY

}

在上面的代码中,我们定义好这个jwt支持的过期时间策略有秒、分、时、天四种四种类型;定义好规则后,我们再来写一个类,用来根据规则生成token相应的过期时间的工具类

public class FailureTimeUtils {

    /**
     * @demand: 根据指定的时间规则和时间生成有效时间
     * @parameters:
     * @creationDate:
     * @email: huangjy19940202@gmail.com
     */
    public static Date creatValidTime(FailureTime failureTime, int jwtValidTime) {
        Date date = new Date();
        if (failureTime.name().equals(FailureTime.SECOND)) {
            return createBySecond(date, jwtValidTime);
        }
        if (failureTime.name().equals(FailureTime.MINUTE)) {
            return createBySecond(date, jwtValidTime * 60);
        }
        if (failureTime.name().equals(FailureTime.HOUR)) {
            return createBySecond(date, jwtValidTime * 60 * 60);
        }
        if (failureTime.name().equals(FailureTime.DAY)) {
            return getDateAfter(date, jwtValidTime);
        }
        return null;
    }

    /**
     * 得到几天后的时间
     *
     * @param day
     * @return
     */
    public static Date getDateAfter(Date date, int day) {
        Calendar now = Calendar.getInstance();
        now.setTime(date);
        now.set(Calendar.DATE, now.get(Calendar.DATE) + day);
        return now.getTime();
    }

    /**
     * 得到几天前的时间
     *
     * @param date
     * @param day
     * @return
     */
    public static Date getDateBefore(Date date, int day) {
        Calendar now = Calendar.getInstance();
        now.setTime(date);
        now.set(Calendar.DATE, now.get(Calendar.DATE) - day);
        return now.getTime();
    }

    /**
     * 得到多少秒之后的时间
     *
     * @param date
     * @param jwtValidTime
     * @return
     */
    public static Date createBySecond(Date date, int jwtValidTime) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.add(Calendar.SECOND, jwtValidTime);
        return calendar.getTime();
    }

}

上面的代码中,我们定义了几个方法,分别是计算几天后的当前时间和多少秒后的当前时间;然后我们再来定义一个枚举用来定义所支持的加密算法;

public enum Header {

    SM3("sm3","国密3加密算法,其算法不可逆,类似于MD5"),
    SM4("sm4","国密4加密算法,对称加密"),
    AES("aes","AES加密算法,对称加密");

    private String code;

    private String details;

    Header(String code, String details) {
        this.code = code;
        this.details = details;
    }
}

在上面代码中,我们定义我们这个JWT支持的加密方式有三种,分别是SM3、SM4、AES,都是属于对称加密算法;SM2是非对称加密算法(此处不做讲解);

下面再定义记录用户数据的部分,我们创建一个JwtClaims 用来存储我们需要保存到JWT中的个性数据,代码如下

import java.util.HashMap;
import java.util.Objects;

public class JwtClaims extends HashMap {
    public JwtClaims() {
        this.put(ID, null);
        this.put(NAME, null);
        this.put(PHONE, null);
        this.put(FAILURETIME, null);
    }
    String ID = "id";
    String NAME = "name";
    String PHONE = "phone";
    /**
     * 有效期
     */
    String FAILURETIME = "failureTime";
    public JwtClaims put(String key, Object value) {
        super.put(key, value);
        return this;
    }
    /**
     * 重写hashCode方法
     *
     * @return
     */
    @Override
    public int hashCode() {
        return Objects.hash(super.hashCode(), this);
    }
}

在上面我们将JWT中需要用到的数据都定义好了后,下面我们就可以开始写JWT相关的算法了,代码如下所示:

@Slf4j
public class Jwts extends ConcurrentHashMap {

    private static Jwts jwts;

    static {
        jwts = new Jwts();
    }

    /**
     * 默认加密密钥
     */
    private final String jwtSafetySecret = "0dcac1b6ec8843488fbe90e166617e34";

    /**
     * 指定加密算法和密钥
     *
     * @param header
     * @param jwtSafetySecret
     * @return
     */
    public static Jwts header(Header header, String jwtSafetySecret) {
        HashMap map &#61; new HashMap<>();
        map.put("code", header);
        map.put("jwtSafetySecret", jwtSafetySecret);
        jwts.put("header", map);return jwts;
    }/**
     * &#64;param jwtClaims
     * &#64;return
     */public Jwts payload(JwtClaims jwtClaims) {
        jwts.put("payload", jwtClaims);return jwts;
    }/**
     * 签名并生成token
     *
     * &#64;return
     */public String compact() throws Exception {// 头部
        HashMap headerObj &#61; (HashMap) jwts.get("header");// 数据
        JwtClaims jwtClaims &#61; (JwtClaims) jwts.get("payload");
        jwtClaims.put("uuid", UUID.randomUUID());// 生成签名
        Object jwtSafetySecretObj &#61; headerObj.get("jwtSafetySecret");// 从头部信息中去除密钥信息
        headerObj.remove("jwtSafetySecret");
        String jwtSafetySecret &#61; jwtSafetySecretObj &#61;&#61; null ? this.jwtSafetySecret : jwtSafetySecretObj.toString();
        Object code &#61; headerObj.get("code");
        String encryptionType &#61; code &#61;&#61; null ? "AES" : code.toString();// 开始签名
        String signature &#61; dataSignature(headerObj, jwtClaims, encryptionType, jwtSafetySecret);// 生成token
        String token &#61; Base64Utils.getBase64(JSONObject.toJSONString(headerObj)) &#43; "."
                &#43; Base64Utils.getBase64(JSONObject.toJSONString(jwtClaims)) &#43; "."
                &#43; signature;
        System.out.println("生成的token为:" &#43; token);return token;
    }/**
     * 生成摘要
     */private static String dataSignature(HashMap headerObj, JwtClaims jwtClaims, String encryptionType, String jwtSafetySecret) throws Exception {
        String dataSignature &#61; null;if (encryptionType.equals(Header.AES.name())) {
            dataSignature &#61; AESUtils.encrypt(JSONObject.toJSONString(headerObj) &#43; JSONObject.toJSONString(jwtClaims), jwtSafetySecret);
        } else if (encryptionType.equals(Header.SM3.name())) {
            dataSignature &#61; SM3Cipher.sm3Digest(JSONObject.toJSONString(headerObj) &#43; JSONObject.toJSONString(jwtClaims), jwtSafetySecret);
        } else if (encryptionType.equals(Header.SM4.name())) {
            dataSignature &#61; new SM4Util().encode(JSONObject.toJSONString(headerObj) &#43; JSONObject.toJSONString(jwtClaims), jwtSafetySecret);
        }return dataSignature;
    }/**
     * &#64;demand: 校验token完整性和时效性   
     */public static Boolean safetyVerification(String tokenString, String jwtSafetySecret) throws Exception {// 有坑&#xff0c;转义字符
        String[] split &#61; tokenString.split("\\.");if (split.length !&#61; 3) {throw new RuntimeException("无效的token");
        }// 头部信息
        HashMap obj &#61; JSON.parseObject(Base64Utils.getFromBase64(split[0]), HashMap.class);// 数据信息
        JwtClaims jwtClaims &#61; JSON.parseObject(Base64Utils.getFromBase64(split[1]), JwtClaims.class);// 签名信息
        String signature &#61; split[2];// 验证token是否在有效期内if (jwtClaims.get("failureTime") !&#61; null) {
            Date failureTime &#61; (Date) jwtClaims.get("failureTime");int i &#61; failureTime.compareTo(new Date());if (i > 0) {throw new RuntimeException("此token已过有效期");
            }
        }// 验证数据篡改
        Object code &#61; obj.get("code");
        String encryptionType &#61; code &#61;&#61; null ? "AES" : code.toString();// 比较签名
        String signatureNew &#61; dataSignature(obj, jwtClaims, encryptionType, jwtSafetySecret);return signature.equals(signatureNew.replaceAll("\r\n","")) ? true : false;
    }
}

在上述的代码中&#xff0c;我们定义了一个静态变量jwts&#xff0c;此处涉及线程安全&#xff0c;暂时先不调整&#xff0c;后期再做优化&#xff1b;在上述代码中&#xff0c;完成了对Header和payload签名操作&#xff0c;然后生成一个新的token&#xff0c;其原理和下图相似&#xff1b;

80bb29749c5f6cf21d61eb27e4ea2322.png
img

然后在代码中我们还完成了对Token认证的操作&#xff0c;其方法为&#xff1a;safetyVerification&#xff0c;在方法中&#xff0c;我们通过对token中的三部分进行签名和比对并且完成token时效性判断(当没有配置token时效性是则表示永久有效)&#xff1b;在这个步骤中可以有效防止数据被篡改&#xff0c;从而保证数据安全&#xff1b;

对JWT加密和解密方面的核心代码大抵如此&#xff0c;其它的引入了一些工具类类似国密加密算法、AES算法及Base64加密算法&#xff0c;这些在完整代码中都有&#xff0c;此处就不一一展示&#xff1b;GitLab地址&#xff1a;

  • https://gitlab.com/qingsongxi/myjwt

代码结构如下图所示&#xff1a;

e8ea3b8bcc088ad19d047a2caf46fb25.png
img

在这里&#xff0c;我们需要定义一个配置文件application.properties&#xff0c;在配置文件中加入相关参数&#xff0c;比如 对称加密密钥、token有效期、需要拦截的URL等等

# 密钥key
jwt.safety.secret&#61;y2W89L6BkRAFljhN
# token有效期
jwt.valid.time&#61;7
# 需要jwt拦截的url
jwt.secret-url&#61;/findCustomerById
# 端口
server.port&#61;80

在这里我们需要定义一个拦截器&#xff0c;用来拦截需要token才能访问的URL&#xff1b;

/**
 * &#64;author: JiaYao
 * &#64;demand: 自定义web拦截器
 */
&#64;Slf4j
&#64;Component
public class WebInterceptor implements HandlerInterceptor {

    /**
     * JWT密钥
     */
    &#64;Value("${jwt.safety.secret}")
    private String jwtSafetySecret;

    &#64;Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {
        //  进入拦截器 WebInterceptor...
        String authorization &#61; request.getHeader("Authorization");
        if (authorization &#61;&#61; null || !authorization.startsWith("Bearer ")) {
            return noAccess403(response);
        } else {
            try {
                String token &#61; authorization.substring(7).replaceAll(" ", "");
                // 验证token的完整性和有效性
                if (StringUtils.isNotEmpty(token) && Jwts.safetyVerification(token, jwtSafetySecret)) {
                    JwtClaims jwtClaims &#61; JSON.parseObject(Base64Utils.getFromBase64(token.split("\\.")[1]), JwtClaims.class);
                    request.setAttribute("claims", jwtClaims);
                    return true;
                }
            } catch (Exception e) {
                e.printStackTrace();
                return noAccess(response);
            }
        }
        return false;
    }

    /**
     * 在未登录状态或登录状态失效时请求需要登录状态才能请求的URL
     *
     * &#64;param httpServletResponse
     * &#64;return
     * &#64;throws Exception
     */
    public boolean noAccess(HttpServletResponse httpServletResponse) throws Exception {
        httpServletResponse.setContentType("text/json; charset&#61;UTF-8");
        httpServletResponse.getWriter().write(JSON.toJSONString(Json.newInstance(Apistatus.CODE_401)));
        return false;
    }

    /**
     * 在未登录状态或登录状态失效时请求需要登录状态才能请求的URL
     *
     * &#64;param httpServletResponse
     * &#64;return
     * &#64;throws Exception
     */
    public boolean noAccess403(HttpServletResponse httpServletResponse) throws Exception {
        httpServletResponse.setContentType("text/json; charset&#61;UTF-8");
        httpServletResponse.getWriter().write(JSON.toJSONString(Json.newInstance(Apistatus.CODE_403)));
        return false;
    }

    &#64;Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object
            o, ModelAndView modelAndView) throws Exception {
    }

    &#64;Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse
            httpServletResponse, Object o, Exception e) throws Exception {
    }
}


/**
 * &#64;author: JiaYao
 * &#64;demand: 将拦截器添加到列表中&#xff0c;即观察者与被观察者
 * &#64;parameters:
 * &#64;creationDate&#xff1a; 2018/12/19 0019 9:16
 */
&#64;Configuration
public class WebRequestInterceptor extends WebMvcConfigurerAdapter {

    &#64;Autowired
    private WebInterceptor webInterceptor;

    /**
     * 需要JWT拦截的Url
     */
    &#64;Value("${jwt.secret-url}")
    private String jwtSecretUrl;
    /**
     * JWT密钥
     */
    &#64;Value("${jwt.safety.secret}")
    private String jwtSafetySecret;

    &#64;Override
    public void addInterceptors(InterceptorRegistry registry) {
        jwtSecretUrl &#61; jwtSecretUrl.replaceAll(" ", "");
        registry.addInterceptor(webInterceptor).addPathPatterns(jwtSecretUrl.split(","));
    }

}

到这里&#xff0c;我们的JWT小工具基本上就算是已经写完了&#xff0c;只需要整合到具体的业务中就可以开始投入使用&#xff0c;下面编写一个访问控制层&#xff0c;在里面定义两个方法&#xff0c;一个是请求登录获取token&#xff0c;另一个是请求需要登录下才能请求的资源&#xff1b;

/**
 * 类 名: LoginController
 */
&#64;Slf4j
&#64;RestController
public class LoginController {

    &#64;Autowired
    private LoginService loginService;

    /**
     * 登录
     *
     * &#64;param customerId
     * &#64;return
     */
    &#64;GetMapping(value &#61; "/login")
    public Json login(String customerId) {
        try {
            return Json.newInstance(loginService.login(customerId));
        } catch (Exception e) {
            log.error("登录失败&#xff0c;错误信息{}", e.getMessage());
            return Json.CODE_500;
        }
    }

    /**
     * 根据用户id查询用户信息
     *
     * &#64;param request
     * &#64;return
     */
    &#64;GetMapping(value &#61; "/findCustomerById")
    public Json findCustomerById(HttpServletRequest request) {
        try {
            String customerId &#61; ((JwtClaims) request.getAttribute("claims")).get("id").toString();
            return Json.newInstance(loginService.findCustomerById(customerId));
        } catch (Exception e) {
            log.error("登录失败&#xff0c;错误信息{}", e.getMessage());
            return Json.CODE_500;
        }
    }

}

然后再来编写一个业务层代码

&#64;Service
public class LoginService {

    &#64;Value("${jwt.safety.secret}")
    private String jwtSafetySecret;

    &#64;Value("${jwt.valid.time}")
    private int jwtValidTime;

    /**
     * 登录
     *
     * &#64;param customerId
     * &#64;return
     */
    public String login(String customerId) {
        Customer customer &#61; new Customer();
        customer.setId(customerId);
        customer.setName("jiayao");
        customer.setPhone("1234567890");
        return createTokenString(customer);
    }

    /**
     * 根据id查用户
     *
     * &#64;param customerId
     * &#64;return
     */
    public Customer findCustomerById(String customerId) {
        Customer customer &#61; new Customer();
        customer.setId(customerId);
        customer.setName("jiayao");
        customer.setPhone("1234567890");
        return customer;
    }

    /**
     * 生成token
     *
     * &#64;param customer
     * &#64;return
     */
    public String createTokenString(Customer customer) {
        String jwtToken &#61; null;
        try {
            jwtToken &#61; Jwts.header(Header.SM4, jwtSafetySecret)
                    .payload(new JwtClaims()
                            .put("id", customer.getId())
                            .put("name", customer.getName())
                            .put("phone", customer.getPhone())
                            .put("failureTime", FailureTimeUtils.creatValidTime(FailureTime.DAY, jwtValidTime))
                            .put("mytest", "我的个性属性"))
                    .compact();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return jwtToken.replaceAll("\r\n","");
    }

}

在上述代码中&#xff0c;有一个createTokenString的方法&#xff0c;此方法可进一步抽取为一个静态的工具类&#xff0c;在里面我们指定加密方式和密钥信息、指定token有效策略&#xff1b;

启动项目后我们通过Postman请求登录接口获取token信息&#xff0c;如下&#xff1a;

d141694bf52cf306455af9f6412366e2.png
img

如上图所示&#xff0c;通过请求登录接口我们成功获取到了token&#xff0c;我们使用这个token去请求一个需要登录才能请求的资源试试&#xff1b;

5b48000fbc75095b2f6d1e0c7bcb53f6.png
img
be5582429f53052d34dabef2bdcce476.png
img

如上图所示&#xff0c;经过拦截器后通过request请求向里面添加属性claims&#xff0c;将用户数据添加进来&#xff0c;然后进入方法后就可以直接拿到用户数据从而确定是哪个用户登录的&#xff0c;即使在多系统情况下&#xff0c;采用同样的逻辑一样是可以解析的&#xff0c;从而实现单点登录&#xff1b;

在上述代码中还有一个问题是&#xff1a;生成的token在有效期内无法被销毁&#xff0c;那么就会存在一个安全问题&#xff0c;即用户多次登录生成多个token&#xff0c;但是前面生成的token还是处于有效状态&#xff0c;无法被及时销毁&#xff1b;鉴于这点&#xff0c;可以采用Redis缓存来解决这个问题&#xff0c;并且还可以实现多个系统共享Redis数据从而保证在在同一时间内只有一个有效的token&#xff1b;

可能有朋友会问&#xff0c;在用户数据的map中&#xff0c;有添加一个UUID是做什么用的&#xff0c;下午在测试的时候我发现对于同一个用户多次生成的token都是相同的&#xff0c;而Jwt(json web token) 中每次生成的都是不一样的&#xff0c;所以我在这里试想了一下&#xff0c;添加一个uuid后可以使数据部分发生变化&#xff0c;从而保证token的唯一性&#xff1b;

GitLab地址&#xff1a;

  • https://link.zhihu.com/?target&#61;https%3A//gitlab.com/qingsongxi/myjwt.git

最后

乐于输出干货的Java技术公众号&#xff1a;Java3y。公众号内有200多篇原创技术文章、海量视频资源、精美脑图&#xff0c;不妨来关注一下&#xff01;

45c575aba89e13b592c672d57195b1ae.png

有帮助&#xff1f;好看&#xff01;转发&#xff01;172b754f6ae7efb439124e55ad2139db.png

推荐阅读

  • 地哥的腾讯面试经历

  • 面试必考的&#xff1a;并发和并行有什么区别&#xff1f;

  • 什么是CountDownLatch&#xff1f;

  • 通俗易懂讲解一条SQL是怎么执行的

  • 互联网公司时尚穿搭指南

  • 什么是DDoS攻击&#xff1f;

  • 花了一天整理了一些我常用的工具




推荐阅读
  • r2dbc配置多数据源
    R2dbc配置多数据源问题根据官网配置r2dbc连接mysql多数据源所遇到的问题pom配置可以参考官网,不过我这样配置会报错我并没有这样配置将以下内容添加到pom.xml文件d ... [详细]
  • Spring源码解密之默认标签的解析方式分析
    本文分析了Spring源码解密中默认标签的解析方式。通过对命名空间的判断,区分默认命名空间和自定义命名空间,并采用不同的解析方式。其中,bean标签的解析最为复杂和重要。 ... [详细]
  • 本文介绍了九度OnlineJudge中的1002题目“Grading”的解决方法。该题目要求设计一个公平的评分过程,将每个考题分配给3个独立的专家,如果他们的评分不一致,则需要请一位裁判做出最终决定。文章详细描述了评分规则,并给出了解决该问题的程序。 ... [详细]
  • 原文地址:https:www.cnblogs.combaoyipSpringBoot_YML.html1.在springboot中,有两种配置文件,一种 ... [详细]
  • baresip android编译、运行教程1语音通话
    本文介绍了如何在安卓平台上编译和运行baresip android,包括下载相关的sdk和ndk,修改ndk路径和输出目录,以及创建一个c++的安卓工程并将目录考到cpp下。详细步骤可参考给出的链接和文档。 ... [详细]
  • 本文介绍了P1651题目的描述和要求,以及计算能搭建的塔的最大高度的方法。通过动态规划和状压技术,将问题转化为求解差值的问题,并定义了相应的状态。最终得出了计算最大高度的解法。 ... [详细]
  • 推荐系统遇上深度学习(十七)详解推荐系统中的常用评测指标
    原创:石晓文小小挖掘机2018-06-18笔者是一个痴迷于挖掘数据中的价值的学习人,希望在平日的工作学习中,挖掘数据的价值, ... [详细]
  • 在springmvc框架中,前台ajax调用方法,对图片批量下载,如何弹出提示保存位置选框?Controller方法 ... [详细]
  • Android实战——jsoup实现网络爬虫,糗事百科项目的起步
    本文介绍了Android实战中使用jsoup实现网络爬虫的方法,以糗事百科项目为例。对于初学者来说,数据源的缺乏是做项目的最大烦恼之一。本文讲述了如何使用网络爬虫获取数据,并以糗事百科作为练手项目。同时,提到了使用jsoup需要结合前端基础知识,以及如果学过JS的话可以更轻松地使用该框架。 ... [详细]
  • SpringBoot整合SpringSecurity+JWT实现单点登录
    SpringBoot整合SpringSecurity+JWT实现单点登录,Go语言社区,Golang程序员人脉社 ... [详细]
  • Android日历提醒软件开源项目分享及使用教程
    本文介绍了一款名为Android日历提醒软件的开源项目,作者分享了该项目的代码和使用教程,并提供了GitHub项目地址。文章详细介绍了该软件的主界面风格、日程信息的分类查看功能,以及添加日程提醒和查看详情的界面。同时,作者还提醒了读者在使用过程中可能遇到的Android6.0权限问题,并提供了解决方法。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • 知识图谱——机器大脑中的知识库
    本文介绍了知识图谱在机器大脑中的应用,以及搜索引擎在知识图谱方面的发展。以谷歌知识图谱为例,说明了知识图谱的智能化特点。通过搜索引擎用户可以获取更加智能化的答案,如搜索关键词"Marie Curie",会得到居里夫人的详细信息以及与之相关的历史人物。知识图谱的出现引起了搜索引擎行业的变革,不仅美国的微软必应,中国的百度、搜狗等搜索引擎公司也纷纷推出了自己的知识图谱。 ... [详细]
  • 本文介绍了多因子选股模型在实际中的构建步骤,包括风险源分析、因子筛选和体系构建,并进行了模拟实证回测。在风险源分析中,从宏观、行业、公司和特殊因素四个角度分析了影响资产价格的因素。具体包括宏观经济运行和宏经济政策对证券市场的影响,以及行业类型、行业生命周期和行业政策对股票价格的影响。 ... [详细]
author-avatar
华力
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有