首页 文章 精选 留言 我的

精选列表

搜索[工作],共10007篇文章
优秀的个人博客,低调大师

#WorkBuddy版工作台,解放双手

日常操作极简零门槛,无需复杂指令与专业技能。打开WorkBuddy后,可直接导入本地Word、Excel、PPT、Markdown等各类办公文件,也可联动腾讯文档在线素材。想要优化文档时,只需鼠标选中页面内文字、表格、幻灯片等任意内容,输入自然语言需求,即可触发AI人机双写模式,AI会直接在原文件内完成润色改写、内容补充、纠错排版、逻辑优化等操作,所有修改实时生效,支持随时手动微调、批注修改,人机协同高效迭代内容。

优秀的个人博客,低调大师

.net core 工作流 WorkFlow 流程

WikeFlow官网:http://www.wikesoft.com WikeFlow2.0帮助文档:http://wikeflowhelp.wikesoft.com/ WikeFlow2.0简介 为什么我们要自已开发流程引擎? .net 平台下面有一些三方的流程引擎,这些流程引擎很多都不好用或者是很难满足用户的业务需求,功能臃肿导致学习难度大,条件限制多导致集成难度大或者根本就没办法集成,无法使用客户自己的权限系统跑流程,针对于行业现状,我们才决定自己研发流程引擎。 1、WikeFlow2.0几乎能满足各行各业的流程审批业务场景,流程引擎最大的优点:小巧,灵活,高度可扩展性,集成容易。 2、WikeFlow2.0的核心团队具有10年以上开发经验,具有非常丰富的流程引擎相关经验。 3、WikeFlow2.0总结了国内外的流程引擎,取长补短,WikeFlow2.0一直也在收集客户的反馈并不断进化。 4、WikeFlow2.0经过公司多个项目的实际运用中不断修改并完善后,我们决定独立成一个产品,产品具体很强的实践经验。 5、WikeFlow2.0有两个版本:支持.Net FrameWork的版本和支持.net Core 的版本,后期可能会支持Java(WikeFlow For Java版本) 6、WikeFlow2.0并不是对WikeFlow的升级,而是完全的重构,如你的项目在使用WikeFlow,需要自行调整代码。 7、WikeFlow2.0的流程审批是支持WinForm程序的,你只需要将每个节点的待办路径设置为你的Winform窗体就可以了。 如果你需要在Winform程序里面进行流程设计,,则在Winform程序里面加一个WebBrower,将流程设计的Url加入到WebBrower。 WikeFlow2.0 主要功能 1、流程审批,一般的业务审批 2、流程会签,一个任务节点多人审批 3、流程驳回,流程不同意 4、流程转办,流程从一个人转交给另外的人处理 5、流程传阅,将流程传递给其他的人查阅 6、流程撤回,提交后可以撤回 7、流程附件,支持节点附件 8、流程审批中修改业务数据 9、流程审批中业务数据权限设 WikeFlow2.0 系统框架 WikeFlow2.0同时有支持.net framework和.net core 的两个版本,WikeFlow for java正在开发中。 后端框架 .net framework asp.net Mvc5和asp.net Webform ORM:EntityFrameWork6.0 后端框架 .net core .net core Mvc ORM:EFCore/Sqlsugar MVC前端框架: bootstrap3,jqGrid,jQuery,layer,jsPlumb等。 Vue3前端框架: Vue3、Element plus、Axios、Vite等。 容易集成: WikeFlow2集成使用方便,如何集成到你的项目中,请查看《如何集成WikeFlow2.0》 WikeFlow2.0 多数据库支持 同时支持多数据库,MySql,Oracle,SQLServer,达梦、人大金仓。 .net framework版本只需要修改Web.config里面的数据库连接字符串就可以实现不同数据库之间的轻松切换。 .net core版本只需要修改StartUp.cs里面的ConfigureServices中的代码就可以实现不同数据库之间的轻松切换。

优秀的个人博客,低调大师

它是如何工作的?

松哥最近辅导了几个小伙伴秋招,有小伙伴在面小红书时遇到这个问题,这个问题想回答全面还是有些挑战,松哥结合之前的一篇旧文和大伙一起来聊聊。 一 无状态登录 1.1 什么是有状态 有状态服务,即服务端需要记录每次会话的客户端信息,从而识别客户端身份,根据用户身份进行请求的处理,典型的设计如 Tomcat 中的 Session。例如登录:用户登录后,我们把用户的信息保存在服务端 session 中,并且给用户一个 cookie 值,记录对应的 session,然后下次请求,用户携带 cookie 值来(这一步有浏览器自动完成),我们就能识别到对应 session,从而找到用户的信息。这种方式目前来看最方便,但是也有一些缺陷,如下: 服务端保存大量数据,增加服务端压力 服务端保存用户状态,不支持集群化部署 1.2 什么是无状态 微服务集群中的每个服务,对外提供的都使用 RESTful 风格的接口。而 RESTful 风格的一个最重要的规范就是:服务的无状态性,即: 服务端不保存任何客户端请求者信息 客户端的每次请求必须具备自描述信息,通过这些信息识别客户端身份 那么这种无状态性有哪些好处呢? 客户端请求不依赖服务端的信息,多次请求不需要必须访问到同一台服务器 服务端的集群和状态对客户端透明 服务端可以任意的迁移和伸缩(可以方便的进行集群化部署) 减小服务端存储压力 1.3 如何实现无状态 无状态登录的流程: 首先客户端发送账户名/密码到服务端进行认证 认证通过后,服务端将用户信息加密并且编码成一个 token(一般是 JWT),返回给客户端 以后客户端每次发送请求,都需要携带认证的 token 服务端对客户端发送来的 token 进行解密,判断是否有效,并且获取用户登录信息 二 JWT 2.1 JWT 简介 JWT,全称是 Json Web Token, 是一种 JSON 风格的轻量级的授权和身份认证规范,可实现无状态、分布式的 Web 应用授权: JWT 作为一种规范,并没有和某一种语言绑定在一起,常用的 Java 实现是 GitHub 上的开源项目 jjwt,地址如下:https://github.com/jwtk/jjwt 2.2 JWT 数据格式 JWT 包含三部分数据: Header:头部,通常头部有两部分信息: 声明类型,这里是JWT 加密算法,自定义 我们会对头部进行 Base64Url 编码(可解码),得到第一部分数据。 Payload:载荷,就是有效数据,在官方文档中(RFC7519),这里给了7个示例信息: iss (issuer):表示签发人 exp (expiration time):表示token过期时间 sub (subject):主题 aud (audience):受众 nbf (Not Before):生效时间 iat (Issued At):签发时间 jti (JWT ID):编号 这部分也会采用 Base64Url 编码,得到第二部分数据。 Signature:签名,是整个数据的认证信息。一般根据前两步的数据,再加上服务的的密钥secret(密钥保存在服务端,不能泄露给客户端),通过 Header 中配置的加密算法生成。用于验证整个数据完整和可靠性。 生成的数据格式如下图: 注意,这里的数据通过 . 隔开成了三部分,分别对应前面提到的三部分,另外,这里数据是不换行的,图片换行只是为了展示方便而已。 2.3 JWT 交互流程 流程图: 步骤翻译: 应用程序或客户端向授权服务器请求授权 获取到授权后,授权服务器会向应用程序返回访问令牌 应用程序使用访问令牌来访问受保护资源(如 API) 因为 JWT 签发的 token 中已经包含了用户的身份信息,并且每次请求都会携带,这样服务的就无需保存用户信息,甚至无需去数据库查询,这样就完全符合了 RESTful 的无状态规范。 2.4 JWT 存在的问题 说了这么多,JWT 也不是天衣无缝,由客户端维护登录状态带来的一些问题在这里依然存在,举例如下: 续签问题,这是被很多人诟病的问题之一,传统的 cookie+session 的方案天然的支持续签,但是 jwt 由于服务端不保存用户状态,因此很难完美解决续签问题,如果引入 redis,虽然可以解决问题,但是 jwt 也变得不伦不类了。 注销问题,由于服务端不再保存用户信息,所以一般可以通过修改 secret 来实现注销,服务端 secret 修改后,已经颁发的未过期的 token 就会认证失败,进而实现注销,不过毕竟没有传统的注销方便。 密码重置,密码重置后,原本的 token 依然可以访问系统,这时候也需要强制修改 secret。 基于第 2 点和第 3 点,一般建议不同用户取不同 secret。 对于上面提到的这些问题,在目前具体的项目实践中,更多的是通过引入 Redis 来解决这些问题。 三 实战 说了这么久,接下来我们就来看看这个东西到底要怎么用? 3.1 环境搭建 首先我们来创建一个 Spring Boot 项目,创建时需要添加 Spring Security 依赖,创建完成后,添加 jjwt 依赖,完整的 pom.xml 文件如下: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> </dependency> 然后在项目中创建一个简单的 User 对象实现 UserDetails 接口,如下: public class User implements UserDetails { private String username; private String password; private List<GrantedAuthority> authorities; public String getUsername() { return username; } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } //省略getter/setter } 这个就是我们的用户对象,先放着备用,再创建一个 HelloController,内容如下: @RestController public class HelloController { @GetMapping("/hello") public String hello() { return "hello jwt !"; } @GetMapping("/admin") public String admin() { return "hello admin !"; } } HelloController 很简单,这里有两个接口,设计是 /hello 接口可以被具有 user 角色的用户访问,而 /admin 接口则可以被具有 admin 角色的用户访问。 3.2 JWT 配置 先准备一个 JWT 操作的工具类: public class JwtUtils { public static final Integer EXPIRE_TIME = 7 * 24 * 60 * 60; public static final Integer REDIS_TOKEN_EXPIRE_TIME = 30 * 60; public static final String TOKEN_ISSUER = "javaboy"; /** * 生成 JWT 字符串 * * @return */ public static String createJWT() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); String jws = Jwts.builder() //设置头部信息(相当于 JWT 字符串第一部分的内容) .header().add("type", "JWT") .and() //相当于 JWT 字符串第二部分的内容 //签名密钥 .signWith(getPrivateKey()) //设置过期时间 .expiration(java.util.Date.from(java.time.Instant.now().plusSeconds(EXPIRE_TIME))) //设置用户名 .subject(authentication.getName()) //设置用户权限,相当于把用户权限转为字符串 admin,user,xxx .claim("authorities", authentication.getAuthorities().stream().map(a -> a.getAuthority()).collect(Collectors.joining(","))) //令牌签发时间 .issuedAt(new Date()) .issuer(TOKEN_ISSUER) .compact(); return jws; } public static PrivateKey getPrivateKey() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException { ClassPathResource resource = new ClassPathResource("private_key.pem"); byte[] privateKeyBytes = FileCopyUtils.copyToByteArray(resource.getInputStream()); String key = new String(privateKeyBytes, StandardCharsets.UTF_8); String privateStr = key .replace("-----BEGIN PRIVATE KEY-----", "") .replaceAll(System.lineSeparator(), "") .replace("-----END PRIVATE KEY-----", ""); byte[] privateKeyDecodedBytes = Base64.getDecoder().decode(privateStr); PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyDecodedBytes); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); PrivateKey privateKey = keyFactory.generatePrivate(keySpec); return privateKey; } public static RSAPublicKey getPublicKey() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException { ClassPathResource resource = new ClassPathResource("public_key.pem"); byte[] publicKeyBytes = FileCopyUtils.copyToByteArray(resource.getInputStream()); String key = new String(publicKeyBytes, StandardCharsets.UTF_8); String publicStr = key .replace("-----BEGIN PUBLIC KEY-----", "") .replaceAll(System.lineSeparator(), "") .replace("-----END PUBLIC KEY-----", ""); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); byte[] publicKeyBase64Bytes = Base64.getDecoder().decode(publicStr); X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKeyBase64Bytes); PublicKey rsaPublicKey = keyFactory.generatePublic(publicKeySpec); return (RSAPublicKey) rsaPublicKey; } public static Authentication verifyToken(String token) { try { Claims claims = Jwts.parser() .verifyWith(getPublicKey()).build() .parseSignedClaims(token).getPayload(); String username = claims.getSubject(); // user,admin,xxx String authorities = claims.get("authorities", String.class); // 解析用户权限 List<SimpleGrantedAuthority> list = Arrays.stream(authorities.split(",")).map(s -> new SimpleGrantedAuthority(s)).collect(Collectors.toList()); return UsernamePasswordAuthenticationToken.authenticated(username, null, list); } catch (Exception e) { //令牌校验失败 return null; } } } 接下来,在用户登录成功的时候,生成 JWT 字符串,并存入到 Redis 中,如下: @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.authorizeHttpRequests(a -> a.anyRequest().authenticated()) .csrf(c -> c.disable()) .sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .formLogin(f -> f .successHandler((req, resp, auth) -> { //登录成功,生成 JWT 字符串 try { String token = JwtUtils.createJWT(); //将 JWT 字符串保存到 redis 中 redisTemplate.opsForValue().set(auth.getName() + ":" + token, auth.getName()); // 设置过期时间 redisTemplate.expire(auth.getName() + ":" + token, JwtUtils.REDIS_TOKEN_EXPIRE_TIME, TimeUnit.SECONDS); resp.setContentType("application/json;charset=utf-8"); RespBean respBean = new RespBean(200, token, null); resp.getWriter().write(new ObjectMapper().writeValueAsString(respBean)); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } catch (InvalidKeySpecException e) { throw new RuntimeException(e); } }) .permitAll()) .logout(l -> l.logoutSuccessHandler((req, resp, auth) -> { // 删除 redis 中的 token String key = auth.getName() + ":" + req.getHeader("Authorization").replace("Bearer ", ""); System.out.println("key = " + key); redisTemplate.delete(key); resp.getWriter().write("logout success"); }).logoutUrl("/logout").permitAll()); http.addFilterAfter(new JwtFilter(redisTemplate), SecurityContextHolderFilter.class); return http.build(); } 最后再来一个校验的过滤器,在用法发送请求的时候,校验 JWT 字符串,如下: public class JwtFilter extends GenericFilterBean { StringRedisTemplate redisTemplate; public JwtFilter(StringRedisTemplate redisTemplate) { this.redisTemplate = redisTemplate; } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) { RespBean result = null; HttpServletRequest req = (HttpServletRequest) servletRequest; HttpServletResponse resp = (HttpServletResponse) servletResponse; resp.setContentType("application/json;charset=utf-8"); try { //如果是登录请求,则直接放行 if (req.getRequestURI().equals("/login")) { filterChain.doFilter(servletRequest, servletResponse); return; } //获取请求头中的token String token = req.getHeader("Authorization"); if (token == null) { result = new RespBean(500, "令牌为空,请求失败", null); PrintWriter out = resp.getWriter(); out.write(new ObjectMapper().writeValueAsString(result)); } else { //验证token token = token.replace("Bearer ", ""); //验证token是否有效 //校验通过,则返回认证后的 Auhtentication 对象,否则返回 null Authentication authentication = JwtUtils.verifyToken(token); String s = redisTemplate.opsForValue().get(authentication.getName() + ":" + token); if (s == null) { // 令牌失效 result = new RespBean(501, "令牌失效,请重新登录", null); resp.getWriter().write(new ObjectMapper().writeValueAsString(result)); } else { //令牌有效,检查令牌过期时间,如果临近过期,则为令牌续期 Long expire = redisTemplate.getExpire(authentication.getName() + ":" + token); if (expire != null && expire < 10 * 60) { //说明令牌还有 10 分钟过期,则重新设置新的过期时间 redisTemplate.expire(authentication.getName() + ":" + token, Duration.ofSeconds(JwtUtils.REDIS_TOKEN_EXPIRE_TIME)); } SecurityContextHolder.getContext().setAuthentication(authentication); filterChain.doFilter(servletRequest, servletResponse); } } } catch (Exception e) { } finally { // 清除上下文 SecurityContextHolder.clearContext(); } } } 关于这个过滤器,我说如下几点: 首先从请求头中提取出 Authorization 字段,这个字段对应的 value 就是用户的 token。 将提取出来的 token 字符串调用 JwtUtils.verifyToken 方法进行验证,将验证结果放到当前的 SecurityContext 中,然后执行过滤链使请求继续执行下去。 3.3 Spring Security 配置 最后,在 Spring Security 中配置这个过滤器。 http.addFilterAfter(new JwtFilter(redisTemplate), SecurityContextHolderFilter.class); 搞定! 3.4 测试 做完这些之后,我们的环境就算完全搭建起来了,接下来启动项目然后在 POSTMAN 中进行测试,如下: 登录成功后返回的字符串就是经过 base64url 转码的 token,一共有三部分,通过一个 . 隔开,我们可以对第一个 . 之前的字符串进行解码,即 Header,如下: 再对两个 . 之间的字符解码,即 payload: 可以看到,我们设置信息,由于 base64 并不是加密方案,只是一种编码方案,因此,不建议将敏感的用户信息放到 token 中。 接下来再去访问 /hello 接口,注意认证方式选择 Bearer Token,Token 值为刚刚获取到的值,如下: 可以看到,访问成功。 四 总结 这就是 JWT 结合 Spring Security 的一个简单用法。

优秀的个人博客,低调大师

了解学习 JSX 的工作方式

过去半年基本都是使用VUE来开发项目,也有了一定的经验了,后续将会推出一套前后端完整的基于VUE的管理系统。从今天开始学习 React ,首先就从定义React组件的JSX开始。那什么是JSX呢?与JavaScript有啥关系?本文就来一起学习。 JSX到底是什么? JSX,即 JavaScript XML,JavaScript 的语法扩展,允许在 JavaScript 文件中编写 HTML (这是个人内心抗拒的原因)。不必通过 JavaScript 对象创建、修改或者拼接 HTML ,可以使用类似 XML 的语法创建元素,编译运行时生成相应的 DOM 元素。 那它看起来怎么样?请看下面的代码: const jsx = <div>Hello React</div>; 如何使用JSX? 那么如何在实践中使用JSX呢?JSX是灵活的,不管哪种编程方式,都需要遵循一定的规则。下面就来逐一介绍。 在JSX中使用表达式 假设希望在HTML模板中使用JavaScript变量,使用花括号 {},可以插值变量以获得动态值: const title = "DevPoint"; const expression = <h1>Hello {title}</h1>; 这里 {title} 将在 h1 元素中替换为 DevPoint 。当然,不仅可以嵌入JavaScript变量,还可以使用更复杂的表达式: const a = 1; const b = 1; const expression = ( <code> {a} + {b} = {a + b} </code> ); 这将输出1+1=2,在处理数组时,表达式也通常用于生成HTML元素列表。假设有一个动态菜单,从API接收数据: { "menu": [ { "name": "Home", "link": "/" }, { "name": "About", "link": "/about" }, { "name": "Contact", "link": "/contact" } ] } 然后,可以使用JSX表达式映射整个数组: const menu = <ul>{menu.map((item) => console.log(item))}</ul>; 在这里,使用JSX表达式中的 Array.map 方法将每个元素记录到控制台,但是如何显示列表元素呢? const menu = ( <ul> {menu.map((item) => ( <li> <a href={item.link}>{item.name}</a> </li> ))} </ul> ); 可以简单地从函数返回所需的元素,这相当于在下面显式写出 return 关键字: const menu = ( <ul> {menu.map((item) => { return ( <li> <a href={item.link}>{item.name}</a> </li> ); })} </ul> ); 表达式还可用于创建条件语句,或使用三元运算符根据变量的值返回不同的HTML元素,如下: { name && <h1>Hello {name}</h1>; } { /* 这里是注释 */} { name ? <h1>Hello {name}</h1> : null; } 如上所见,JSX中的注释也用于表达式中。如果要在一行前面写//的话,它也会被打印到最终的HTML文件中,因为它被解释为自由流文本。因此,在使用注释时需要使用表达式。 在JSX中使用属性 正如上面所看到的,也可以像平常一样在JSX元素中使用HTML属性。还可以对属性使用JSX表达式,或者如果使用的是静态值,则可以使用常规HTML中的引号来定义它们: <a href={item.link} target="_blank"> {item.name} </a>; 在JSX中,这些称为 props,需要注意的一点是,当涉及到使用破折号的属性时,JSX 使用驼峰大小写。例如,如果要添加 aria 属性,将执行以下操作: <div aria-label="Label"></div> <div aria-checked="true"></div> <div tab-index="0"></div> <div ariaLabel="Label"></div> <div ariaChecked="true"></div> <div tabIndex="0"></div> 在JSX中使用属性时还有一个特殊的例外,那就是使用HTML类。由于单词 class 在JavaScript中是一个保留关键字,因此需要改用 className: <div class="carousel"></div> <div className="carousel"></div> 始终关闭JSX标记 当使用自动关闭标记或使用纯粹用于传递视觉信息的元素且这些元素没有任何子元素时,请确保始终关闭标签,否则,将会出现错误: {/* 错误的方式 */} <br> <img> <div> {/* 正确的方式 */} <br /> <img /> <div className="loader" /> 返回JSX 需要指出的另一条重要规则是,一次只能返回一个JSX元素。请看下面的 HTML 元素: <span className="tooltip">...</span> <div className="tooltip-backdrop" /> 这个例子是无效的,因为试图返回多个相邻的JSX元素,为了修复它,需要将其包装在父级中: <span className="tooltip-container"> <span className="tooltip">...</span> <div className="tooltip-backdrop" /> </span> 或者,如果不想用不必要的HTML元素污染DOM树,还可以使用一个称为片段的特殊标记: <React.Fragment> <span className="tooltip">...</span> <div className="tooltip-backdrop" /> </React.Fragment> {/* 或者 */} <Fragment> <span className="tooltip">...</span> <div className="tooltip-backdrop" /> </Fragment> {/* 或者 */} <> <span className="tooltip">...</span> <div className="tooltip-backdrop" /> </> 以上三个示例将生成相同的代码,都能正常运行,并且不会向HTML文件中添加额外的DOM元素。 存储JSX JSX元素也可以存储在变量中,并传递给其它JSX元素使用。但是,很可能会涉及到将JSX元素存储在函数中的情况: const Slider = () => ( <div>这是一个组件</div> ); <Slider /> 此外,建议将函数大写,否则,它将被视为HTML元素,例外的情况是使用对象方法,例如: const component = { slider() { return <div>这是一个组件</div> } }; <component.slider /> 通过使用函数,还可以传递可用于HTML元素的插件: const Slider = ({ size }) => ( <div className={size}>这是一个可以控制尺寸的组件</div> ); <Slider size="large" /> <Slider size="medium" /> <Slider size="small" /> 如上所见,不仅限于使用HTML属性的名称,还可以使用自定义名称。 JSX生成成什么? 那么JSX究竟生成了什么呢?浏览器无法理解JSX语法,因此 React 需要将其编译为浏览器能够理解的 JavaScript 函数调用。这些JavaScript函数调用最终将创建和更新必要的HTML元素。以下面的代码片段为例,它将生成以下函数调用: const jsx = <div>Welcome to React</div>; React.createElement("div", null, "Welcome to React"); const jsx = <div className="carousel" />; React.createElement("div", { className: "carousel" }); 如果 JSX 元素有子元素,那么这些子元素也将被编译为 React.createElement 调用,并最终生成在 JavaScript 包中,如下: React.createElement(component, props, ...children); 同样,如果对组件使用小写,则这些组件将被视为HTML元素,因此请确保将它们大写: <component />; React.createElement("component"); <Component />; React.createElement(Component); <component.slider />; React.createElement(component.slider); 总结 JSX 可以简化代码,使其更易于使用,同时也使其更具可读性。为了回顾本教程的重要部分,关于 JSX 与 JavaScript 的连接,以下是一些注意事项: JSX 是 JavaScript 的语法扩展,代表 JavaScript XML。 React 可以使用或不使用 JSX 编写,尽管如此,强烈建议使用 JSX。 JSX 支持使用大括号来使用表达式。 JSX 中的属性是用驼峰命名法编写的。 JSX 元素必须始终关闭。如果不涉及子元素,则元素必须是闭合(成对)的。 一次只能返回一个 JSX,如果需要返回多个元素,请使用片段。 函数可以返回 JSX 元素。 JSX 生成为 React.createElement 函数调用。

优秀的个人博客,低调大师

几种常见负载均衡算法工作原理

轮询 顾名思义将请求按顺序轮流地分配到后端服务器上,它均衡地对待后端的每一台服务器,而不关心服务器实际的连接数和当前的系统负载。 优点 缺点 服务器请求数据相同 服务器压力不同,不适合根据服务器配置不同的情况 随机 通过系统的随机算法,根据后端服务器的列表大小值来随机选取其中的一台服务器进行访问。由概率统计理论可以得知,随着客户端调用服务端的次数增多,其实际效果越来越接近于平均分配调用量到后端的每一台服务器,也就是轮询的结果。 优点 缺点 使用简单 服务器压力不同,不适合根据服务器配置不同的情况 源地址哈希 源地址哈希的思想是根据获取客户端的IP地址,通过哈希函数计算得到的一个数值,用该数值对服务器列表的大小进行取模运算,得到的结果便是客服端要访问服务器的序号。采用源地址哈希法进行负载均衡,同一IP地址的客户端,当后端服务器列表不变时,它每次都会映射到同一台后端服务器进行访问。 优点 缺点 将来自同一IP地址的请求,同一会话期内,转发到相同的服务器;实现会话粘滞。 目标服务器宕机后,会话会丢失 加权轮询法 不同的后端服务器可能机器的配置和当前系统的负载并不相同,因此它们的抗压能力也不相同。给配置高、负载低的机器配置更高的权重,让其处理更多的请;而配置低、负载高的机器,给其分配较低的权重,降低其系统负载,加权轮询能很好地处理这一问题,并将请求顺序且按照权重分配到后端。 优点 缺点 根据权重,调节转发服务器的请求数目 使用相对复杂 加权随机法 与加权轮询法一样,加权随机法也根据后端机器的配置,系统的负载分配不同的权重。不同的是,它是按照权重随机请求后端服务器,而非顺序。 最小连接数法 最小连接数算法比较灵活和智能,由于后端服务器的配置不尽相同,对于请求的处理有快有慢,它是根据后端服务器当前的连接情况,动态地选取其中当前积压连接数最少的一台服务器来处理当前的请求,尽可能地提高后端服务的利用效率,将负责合理地分流到每一台服务器。 优点 缺点 根据服务器当前的请求处理情况,动态分配 算法实现相对复杂,需要监控服务器请求连接数

资源下载

更多资源
Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册