• [技术干货] 业务开发时,接口不能对外暴露怎么办?
    1. 内外网接口微服务隔离 将对外暴露的接口和对内暴露的接口分别放到两个微服务上,一个服务里所有的接口均对外暴露,另一个服务的接口只能内网服务间调用。 该方案需要额外编写一个只对内部暴露接口的微服务,将所有只能对内暴露的业务接口聚合到这个微服务里,通过这个聚合的微服务,分别去各个业务侧获取资源。 该方案,新增一个微服务做请求转发,增加了系统的复杂性,增大了调用耗时以及后期的维护成本。 2. 网关 + redis 实现白名单机制 在 redis 里维护一套接口白名单列表,外部请求到达网关时,从 redis 获取接口白名单,在白名单内的接口放行,反之拒绝掉。 该方案的好处是,对业务代码零侵入,只需要维护好白名单列表即可; 不足之处在于,白名单的维护是一个持续性投入的工作,在很多公司,业务开发无法直接触及到 redis,只能提工单申请,增加了开发成本;另外,每次请求进来,都需要判断白名单,增加了系统响应耗时,考虑到正常情况下外部进来的请求大部分都是在白名单内的,只有极少数恶意请求才会被白名单机制所拦截,所以该方案的性价比很低。 3. 方案三 网关 + AOP 相比于方案二对接口进行白名单判断而言,方案三是对请求来源进行判断,并将该判断下沉到业务侧。避免了网关侧的逻辑判断,从而提升系统响应速度。 我们知道,外部进来的请求一定会经过网关再被分发到具体的业务侧,内部服务间的调用是不用走外部网关的(走 k8s 的 service)。 根据这个特点,我们可以对所有经过网关的请求的header里添加一个字段,业务侧接口收到请求后,判断header里是否有该字段,如果有,则说明该请求来自外部,没有,则属于内部服务的调用,再根据该接口是否属于内部接口来决定是否放行该请求。 该方案将内外网访问权限的处理分布到各个业务侧进行,消除了由网关来处理的系统性瓶颈;同时,开发者可以在业务侧直接确定接口的内外网访问权限,提升开发效率的同时,增加了代码的可读性。 当然该方案会对业务代码有一定的侵入性,不过可以通过注解的形式,最大限度的降低这种侵入性。  具体实操 下面就方案三,进行具体的代码演示。 首先在网关侧,需要对进来的请求header添加外网标识符: from=public @Componentpublic class AuthFilter implements GlobalFilter, Ordered { @Override public Mono < Void > filter ( ServerWebExchange exchange, GatewayFilterChain chain ) { return chain.filter( exchange.mutate().request( exchange.getRequest().mutate().header("id", "").header("from", "public").build()) .build() ); } @Override public int getOrder () { return 0; } }接着,编写内外网访问权限判断的AOP和注解@Aspect@Component@Slf4jpublic class OnlyIntranetAccessAspect { @Pointcut ( "@within(org.openmmlab.platform.common.annotation.OnlyIntranetAccess)" ) public void onlyIntranetAccessOnClass () {} @Pointcut ( "@annotation(org.openmmlab.platform.common.annotation.OnlyIntranetAccess)" ) public void onlyIntranetAccessOnMethed () { } @Before ( value = "onlyIntranetAccessOnMethed() || onlyIntranetAccessOnClass()" ) public void before () { HttpServletRequest hsr = (( ServletRequestAttributes ) RequestContextHolder.getRequestAttributes()) .getRequest (); String from = hsr.getHeader ( "from" ); if ( !StringUtils.isEmpty( from ) && "public".equals ( from )) { log.error ( "This api is only allowed invoked by intranet source" ); throw new MMException ( ReturnEnum.C_NETWORK_INTERNET_ACCESS_NOT_ALLOWED_ERROR); } } } @Target({ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface OnlyIntranetAccess {}最后,在只能内网访问的接口上加上@OnlyIntranetAccess注解即可@GetMapping ( "/role/add" )@OnlyIntranetAccesspublic String onlyIntranetAccess() { return "该接口只允许内部服务调用";版权声明:本文为CSDN博主「肥肥技术宅」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:cid:link_0
  • [技术干货] 切面实现下单请求防重提交功能(自定义注释@repeatSubmit)
    切面实现下单请求防重提交功能(自定义注释@repeatSubmit) 该切面功能适用场景 下单请求多次提交,导致生成多个相同的订单 解决方案 前端解决:限制点击下单按钮为1次后失效。不足:用户体验下降,能绕过前端 后端解决:防重提交切面解决,自定义注释实现该功能(如下) 步骤: 自定义注释类RepeatSubmit 创建切面并有该注释绑定,在切面类实现防重提交功能: 方式一:引入redission进行加锁5秒,原理redis的setAbsent 方式二:将token存入redis中,下单成功删除token,下单前需要调用获取token接口才能成功下单(类似于加锁,和方式一原理相同) RepeatSubmit /** * 自定义防重提交 */@Documented@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface RepeatSubmit { /** * 防重提交类型。 方法、令牌 */ enum Type {PARAM, TOKEN} /** * 默认防重提交,是方法参数 * @return */ Type limitType() default Type.PARAM; /** * 加锁过期时间,默认5秒 * @return */ long lockTime() default 5;} 自定义切面类/** * 定义一个切面类 */@Aspect@Component@Slf4jpublic class RepeatSubmitAspect { @Autowired private StringRedisTemplate redisTemplate; @Autowired private RedissonClient redissonClient; /** * 定义 @Pointcut注解表达式, * 方式一:@annotation:当执行的方法上拥有指定的注解时生效(我们采用这) */ @Pointcut("@annotation(repeatSubmit)") public void pointCutNoRepeatSubmit(RepeatSubmit repeatSubmit) { } /** * 环绕通知, 围绕着方法执行 * * @param joinPoint * @param repeatSubmit * @return * @throws Throwable * @Around 可以用来在调用一个具体方法前和调用后来完成一些具体的任务。 */ @Around("pointCutNoRepeatSubmit(repeatSubmit)") public Object around(ProceedingJoinPoint joinPoint, RepeatSubmit repeatSubmit) throws Throwable { HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); Long accountNo = LoginInterceptor.threadLocal.get().getAccountNo(); // 记录成功或者失败 Boolean res = false; // 防重提交类型 String type = repeatSubmit.limitType().name(); if (type.equalsIgnoreCase(RepeatSubmit.Type.PARAM.name())) { //方式一,参数形式防重提交 long lockTime = repeatSubmit.lockTime(); String ipAddr = CommonUtil.getIpAddr(request); MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); Method method = methodSignature.getMethod(); String className = method.getDeclaringClass().getName(); String key = "order-server:repeat_submit"+CommonUtil.MD5(String.format("%s-%s-%s-%s", ipAddr, className, method, accountNo)); // 加锁 //res = redisTemplate.opsForValue().setIfAbsent(key,"1",lockTime, TimeUnit.SECONDS); RLock lock = redissonClient.getLock(key); // 尝试加锁,最多等待2秒,上锁以后5秒自动解锁 [lockTime默认为5s, 可以自定义] res = lock.tryLock(2, lockTime, TimeUnit.SECONDS); } else if (type.equalsIgnoreCase(RepeatSubmit.Type.TOKEN.name())) { //方式二,令牌形式防重提交 String requestToken = request.getHeader("request-token"); if (StringUtils.isBlank(requestToken)) { throw new BizException(BizCodeEnum.ORDER_CONFIRM_TOKEN_EQUAL_FAIL); } String key = String.format(RedisKey.SUBMIT_ORDER_TOKEN_KEY, accountNo, requestToken); /** * 提交表单的token key * key是 order:submit:accountNo:token,然后直接删除成功则完成 */ res = redisTemplate.delete(key); } if (!res) { log.error("订单请求重复提交"); return null; } log.info("环绕通知执行前"); Object obj = joinPoint.proceed(); log.info("环绕通知执行后"); return obj; }}RedissionConfiguration配置类(用于加锁)@Configurationpublic class RedissionConfiguration { @Value("${spring.redis.host}") private String redisHost; @Value("${spring.redis.port}") private String redisPort; @Value("${spring.redis.password}") private String redisPwd; /** * 配置分布式锁的redisson * @return */ @Bean public RedissonClient redissonClient(){ Config config = new Config(); //单机方式 config.useSingleServer().setPassword(redisPwd).setAddress("redis://"+redisHost+":"+redisPort); //集群 //config.useClusterServers().addNodeAddress("redis://192.31.21.1:6379","redis://192.31.21.2:6379") RedissonClient redissonClient = Redisson.create(config); return redissonClient; } /** * 集群模式 * 备注:可以用"rediss://"来启用SSL连接 */ /*@Bean public RedissonClient redissonClusterClient() { Config config = new Config(); config.useClusterServers().setScanInterval(2000) // 集群状态扫描间隔时间,单位是毫秒 .addNodeAddress("redis://127.0.0.1:7000") .addNodeAddress("redis://127.0.0.1:7002"); RedissonClient redisson = Redisson.create(config); return redisson; }*/}使用说明:在下单接口标注@RepeatSubmit(limitType = RepeatSubmit.Type.TOKEN) 或者@RepeatSubmit(limitType = RepeatSubmit.Type.PARAM) /** * 下单前获取令牌,用于防重提交 * @return */ @GetMapping("token") public JsonData getOrderToken() { Long accountNo = LoginInterceptor.threadLocal.get().getAccountNo(); String token = CommonUtil.getStringNumRandom(32); String key = String.format(RedisKey.SUBMIT_ORDER_TOKEN_KEY, accountNo, token); // token 过期时间30分钟 redisTemplate.opsForValue().set(key, String.valueOf(Thread.currentThread().getId()), 30, TimeUnit.MINUTES); return JsonData.buildSuccess(token); } @PostMapping("confirm") @RepeatSubmit(limitType = RepeatSubmit.Type.TOKEN) public void confirmOrder(@RequestBody ConfirmOrderRequest orderRequest, HttpServletResponse response) { // TODO 下单业务 }转载自https://www.cnblogs.com/xietingwei/p/17615725.html
  • [技术干货] 剖析JWT,及其使用案例
     什么是JWT JWT 是一个开放标准,它定义了一种用于简洁,自包含的用于通信双方之间以 JSON 对象的形式安全传递信息的方法。 可以使用 HMAC 算法或者是 RSA 的公钥密钥对进行签名 简单来说: 就是通过一定规范来生成token,然后可以通过解密算法逆向解密token,这样就可以获取用户信息 优点 生产的token可以包含基本信息,比如id、用户昵称、头像等信息,避免再次查库 存储在客户端,不占用服务端的内存资源 缺点 token是经过base64编码,所以可以解码,因此token加密前的对象不应该包含敏感信息,如用户权限,密码等 如果没有服务端存储,则不能做登录失效处理,除非服务端改秘钥 JWT格式组成 头部、负载、签名 header+payload+signature 头部:主要是描述签名算法 负载:主要描述是加密对象的信息,如用户的id等,也可以加些规范里面的东西,如iss签发者,exp 过期时间,sub 面向的用户 签名:主要是把前面两部分进行加密,防止别人拿到token进行base解密后篡改token 关于jwt客户端存储 可以存储在cookie,localstorage和sessionStorage里面 代码实现 依赖引入 <!-- JWT相关 --><dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.7.0</version></dependency>JWTUtil类,封装了生产token方法和校验token方法@Slf4jpublic class JWTUtil { /** * 主题 */ private static final String SUBJECT = "xtw"; /** * 加密秘钥 */ private static final String SECRET = "xtw.com"; /** * 令牌前缀 */ private static final String TOKEN_PREFIX = "xtwcloud-link"; /** * 过期时间 7天 */ private static final long EXPIRED = 1000*60*60*24*7; /** * 生成token * @param loginUser * @return */ public static String geneJsonWebToken(LoginUser loginUser){ if(loginUser == null){ throw new NullPointerException("对象为空"); } String token = Jwts.builder().setSubject(SUBJECT) // 配置payload .claim("head_img",loginUser.getHeadImg()) .claim("account_no",loginUser.getAccountNo()) .claim("username",loginUser.getUserName()) .claim("mail",loginUser.getMail()) .claim("phone",loginUser.getPhone()) .claim("auth",loginUser.getAuth()) .setIssuedAt(new Date()) .setExpiration(new Date(CommonUtil.getCurrentTimestamp()+EXPIRED)) .signWith(SignatureAlgorithm.HS256,SECRET).compact(); token = TOKEN_PREFIX + token; return token; } /** * 校验token * @param token * @return */ public static Claims checkJWT(String token){ try { final Claims claims = Jwts.parser().setSigningKey(SECRET) .parseClaimsJws(token.replace(TOKEN_PREFIX, "")) .getBody(); return claims; }catch (Exception e){ log.info("jwt 校验失败"); return null; } }}案例 用户初次登录或者token过期,登录成功返回JWT令牌 public JsonData login(AccountLoginRequest loginRequest) { // 校验用户账号和密码,等... TODO // 对象变迁 loginRequest -> DO -> DTO(loginUser) // loginRequest:前端输入 // DO:根据loginRequest从数据库中提取 // DTO(loginUser):剔除了DO中的敏感字段,用于生成token // 密码正确 生成JWT_token String token = JWTUtil.geneJsonWebToken(loginUser); return JsonData.buildSuccess(token);}登录后,客户端存储了token,网页跳转只需要校验token//拦截器配置@Configuration@Slf4jpublic class InterceptorConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()) // 拦截路径,需要token的接口 .addPathPatterns("[拦截url]") // 放行路径,不需要token的接口,如注册登录接口等... .excludePathPatterns("[放行url]"); }}// 登录拦截器--进行用户的token校验(用户登录成功后网页跳转)@Slf4jpublic class LoginInterceptor implements HandlerInterceptor { //本地线程:在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。 public static ThreadLocal<LoginUser> threadLocal = new ThreadLocal<>(); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行OPTIONS请求 if(HttpMethod.OPTIONS.toString().equalsIgnoreCase(request.getMethod())){ response.setStatus(HttpStatus.NO_CONTENT.value()); return true; } // 从前端获取token String accountToken = request.getHeader("token"); if(StringUtils.isBlank(accountToken)){ accountToken = request.getParameter("token"); } if(StringUtils.isNotBlank(accountToken)){ // 校验token,获取claims Claims claims = JWTUtil.checkJWT(accountToken); if(claims == null){ // 未登录 CommonUtil.sendJsonMessage(response, JsonData.buildResult(BizCodeEnum.ACCOUNT_UNLOGIN)); return false; } // 获取用户字段信息 Long accountNo = Long.parseLong(claims.get("account_no").toString()); String headImg = (String) claims.get("head_img"); String username = (String) claims.get("username"); String mail = (String) claims.get("mail"); String phone = (String) claims.get("phone"); String auth = (String) claims.get("auth"); // LoginUser对象需加@Data@Builder@AllArgsConstructor@NoArgsConstructor LoginUser loginUser = LoginUser.builder() .accountNo(accountNo) .auth(auth) .phone(phone) .headImg(headImg) .mail(mail) .userName(username) .build(); //request.setAttribute("loginUser",loginUser); //通过threadlocal threadLocal.set(loginUser); return true; } return false; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { }}转载自https://www.cnblogs.com/xietingwei/p/17567018.html
  • [技术干货] Future和CompletableFuture区别
    Future :获取异步返回的结果需要使用轮询的方式,消耗cup ExecutorService executorService = Executors.newFixedThreadPool(10); Future<String> future = executorService.submit(()->{ try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } return "future"; }); while(true){ if(future.isDone()){ System.out.println(future.get()); break; } }CompletableFuture:采用观察者模式,阻塞获取异步返回的结果,性能得到优化 System.out.println("=============CompletableFuture==================="); CompletableFuture testFuture1 = CompletableFuture.supplyAsync(()->{ return "丽丽1"; }).thenApply((element)->{ System.out.println("testFuture1后续操作:"+element); return "丽丽2"; }); System.out.println(testFuture1.get()); System.out.println("=============CompletableFuture==================="); CompletableFuture testFuture2 = CompletableFuture.supplyAsync(()->{ return "丽丽1"; }).thenAccept((element)->{ System.out.println("testFuture2后续操作:"+element); }); System.out.println(testFuture2.get());CompletableFuture的使用明细官方文档:cid:link_0* runAsync 无返回值 * supplyAsync 有返回值 * * thenAccept 无返回值 * thenApply 有返回值 * thenRun 不关心上一步执行结果,执行下一个操作 * get() 为阻塞获取 可设置超时时间 避免长时间阻塞实现接口 AsyncFunction 用于请求分发 定义一个callback回调函数,该函数用于取出异步请求的返回结果,并将返回的结果传递给ResultFuture 对DataStream的数据使用Async操作例子/** * An implementation of the 'AsyncFunction' that sends requests and sets the callback. * 通过向数据库发送异步请求并设置回调方法 */ class AsyncDatabaseRequest extends RichAsyncFunction<String, Tuple2<String, String>> { /** The database specific client that can issue concurrent requests with callbacks 可以异步请求的特定数据库的客户端 */ private transient DatabaseClient client; @Override public void open(Configuration parameters) throws Exception { client = new DatabaseClient(host, post, credentials); } @Override public void close() throws Exception { client.close(); } @Override public void asyncInvoke(String key, final ResultFuture<Tuple2<String, String>> resultFuture) throws Exception { // issue the asynchronous request, receive a future for result // 发起一个异步请求,返回结果的 future final Future<String> result = client.query(key); // set the callback to be executed once the request by the client is complete // the callback simply forwards the result to the result future // 设置请求完成时的回调.将结果传递给 result future CompletableFuture.supplyAsync(new Supplier<String>() { @Override public String get() { try { return result.get(); } catch (InterruptedException | ExecutionException e) { // Normally handled explicitly. return null; } } }).thenAccept( (String dbResult) -> { resultFuture.complete(Collections.singleton(new Tuple2<>(key, dbResult))); }); } } // create the original stream // 创建一个原始的流 DataStream<String> stream = ...; // apply the async I/O transformation // 添加一个 async I/O ,指定超时时间,和进行中的异步请求的最大数量 DataStream<Tuple2<String, String>> resultStream = AsyncDataStream.unorderedWait(stream, new AsyncDatabaseRequest(), 1000, TimeUnit.MILLISECONDS, 100); 注意事项Timeout:定义请求超时时间,异步请求多久没完成会被认为是超时了Capacity:定义了同时进行的异步请求的数量,可以限制并发请求数量,不会积压过多的请求超时处理:默认当一个异步 I/O 请求超时时,会引发异常并重新启动作业。 如果要处理超时,可以覆盖该AsyncFunction的timeout方法来自定义超时之后的处理方式响应结果的顺序:AsyncDataStream包含两种输出模式,unorderedWait无序:响应结果的顺序与异步请求的顺序不同orderedWait有序:响应结果的顺序与异步请求的顺序相同转载自https://www.cnblogs.com/xietingwei/p/17647249.html​​​​​​​
  • [技术干货] 过滤器和拦截器的区别
    一、过滤器1.什么是过滤器?  过滤器是一种用于JavaWeb应用程序中的组件,它可以拦截HTTP请求和响应,以实现一些特定的功能。  过滤器可以对请求和响应进行修改,可以阻止请求进入Servlet,也可以修改响应返回给客户端。2.过滤器的主要作用登录验证:检查用户是否已经登录,如果没有登录则跳转到登录页面。权限控制:检查用户是否有访问某个资源的权限,如果没有则提示错误信息或者跳转到其他页面。编码转换:设置请求和响应的字符编码,解决中文乱码问题。敏感词过滤:替换或者屏蔽掉请求参数或者响应内容中的敏感词汇。日志记录:记录用户的访问信息,如IP地址,访问时间,访问路径等。3.过滤器的底层实现原理是基于回调函数的  当一个请求到达服务器时,服务器会根据请求的URL匹配相应的过滤器链。过滤器链是由一个或多个过滤器组成的,按照配置的顺序依次执行。  每个过滤器都实现了Filter接口,该接口定义了三个方法:init(FilterConfig filterConfig):初始化方法,在过滤器创建时调用一次,可以用来获取过滤器配置参数。doFilter(ServletRequest request, ServletResponse response, FilterChain chain):过滤方法,在每次请求被拦截时调用,可以用来执行具体的过滤逻辑。该方法有三个参数:request表示请求对象,response表示响应对象,chain表示过滤器链对象。通过调用chain.doFilter(request, response)方法,可以将请求传递给下一个过滤器或者目标Servlet。如果不调用该方法,则请求被阻止,不会继续处理。destroy():销毁方法,在过滤器被销毁时调用一次,可以用来释放资源。  使用过滤器有两种方式:    注解方式和XML方式。    注解方式是通过在过滤器类上添加@WebFilter注解来配置过滤器的属性,如拦截路径,初始化参数等。    XML方式是通过在web.xml文件中添加<filter>和<filter-mapping>标签来配置过滤器的属性。‘    两种方式都可以实现相同的功能,但是注解方式更简洁方便。4.过滤器的使用场景防止SQL注入攻击:通过对请求参数进行检查和转义,避免恶意用户输入SQL语句造成数据库被篡改或泄露。防止跨站脚本攻击(XSS):通过对请求参数和响应内容进行编码或过滤,避免恶意用户输入HTML或JavaScript代码造成网页被篡改或用户信息被窃取。防止跨站请求伪造(CSRF):通过在表单中添加隐藏字段或者在请求头中添加自定义字段,并在服务器端进行验证,避免恶意用户利用用户已登录的身份发送非法请求。实现缓存机制:通过在响应头中添加缓存相关的字段,并在服务器端判断请求是否命中缓存,可以提高网站性能和用户体验。实现压缩机制:通过在响应头中添加压缩相关的字段,并在服务器端对响应内容进行压缩,可以减少网络传输的数据量和时间。二、拦截器1.什么是拦截器?  拦截器是一种用于JavaWeb应用程序中的组件,它可以在Servlet执行之前拦截HTTP请求,并对请求进行一些处理,比如登录验证,权限控制,日志记录,编码转换等。  拦截器可以根据需要决定是否继续执行Servlet或者返回响应。2.拦截器的底层实现原理是基于动态代理的  当一个请求到达服务器时,服务器会根据请求的URL匹配相应的拦截器链。拦截器链是由一个或多个拦截器组成的,按照配置的顺序依次执行。  每个拦截器都实现了HandlerInterceptor接口,该接口定义了三个方法:preHandle(HttpServletRequest request, HttpServletResponse response, Object handler):在Servlet执行之前调用,可以对请求进行预处理,也可以返回false来阻止请求继续执行。postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView):在Servlet执行之后调用,可以对响应进行后处理,也可以修改ModelAndView对象。afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex):在整个请求完成之后调用,可以用来释放资源或者处理异常。  使用拦截器有两种方式:  注解方式和XML方式。  注解方式是通过在拦截器类上添加@WebMvcConfigurer注解来配置拦截器的属性,如拦截路径,排除路径等。  XML方式是通过在web.xml文件中添加mvc:interceptors标签来配置拦截器的属性。两种方式都可以实现相同的功能,但是注解方式更简洁方便。3.拦截器的使用场景登录验证:检查用户是否已经登录,如果没有登录则跳转到登录页面或者返回错误信息。权限控制:检查用户是否有访问某个资源的权限,如果没有则提示错误信息或者跳转到其他页面。日志记录:记录用户的访问信息,如IP地址,访问时间,访问路径等。编码转换:设置请求和响应的字符编码,解决中文乱码问题。性能监控:记录请求的开始时间和结束时间,计算请求的处理时间和响应时间。三、拦截器和过滤器的主要区别拦截器是基于动态代理的,它可以在目标方法执行前后或者异常时进行增强处理。过滤器是基于函数回调的,它只能在请求进入或者离开容器时进行过滤操作。拦截器是SpringMVC框架提供的,它只对控制器方法有效,而过滤器是Servlet规范定义的,它对所有的请求都有效,包括静态资源。拦截器可以访问控制器方法的上下文信息,如方法参数,返回值,异常等。过滤器只能访问请求和响应对象,不能获取具体的业务逻辑信息。拦截器可以通过返回值来控制请求是否继续执行,如果返回false,则请求被阻止。过滤器没有这样的功能,只能通过转发或者重定向来改变请求的流程。拦截器可以实现多个拦截器链式调用,而且调用顺序是可控的。过滤器也可以有多个过滤器依次执行,但是调用顺序是由容器决定的。转载自https://www.cnblogs.com/FangwayLee/p/17652998.html
  • add as library是什么?有什么用?如何打开?
    在很多地方看见add as library,但就是不知道这是干啥的。上网搜,发现并没有直接介绍这个的,经过一段时间的信息搜集,总结出以下几个知识点,希望给一些和我一样的小小白经验学习,希望每个小白都能成为自己心中向往的大佬。 一. add as library是什么? add as library是IntelliJ IDEA里面的一个功能,当然eclipse里面也有,多用于java开发和Android开发。 二. add as library有什么用?         多用于将将外部jar(lib / * .jar)添加到IntelliJ IDEA项目。        通俗点说,就是你做项目的时候需要一些外部jar包,比如我们用java连接数据库时用到了阿里巴巴公司的数据库连接池Druid,我们就需要导入Druid相关的jar包和配置文件,这些jar包和配置文件就属于外部jar包和配置文件。一般我们都是在项目路径下单独再建一个libs目录来存放这些东西。但直接复制粘贴到这个lib文件夹里面是不够的,我们还要把这些文件和jar包  add as library  来保证各种依赖关系。 三. add as library如何调出? 点击你要add as library的文件或jar包,右键选择add as library即可。  当然还有其他当如jar包的方法,还比较常用的是点File->Project Structure->Moudules->Dependenceies-> 右侧的 + 即可添加jar包 ———————————————— 原文链接:https://blog.csdn.net/doubleguy/article/details/104947149 
  • [技术干货] 关于${pageContext.request.contextPath}的理解
    ${pageContext.request.contextPath}是JSP取得绝对路径的方法,等价于<%=request.getContextPath()%> 。也就是取出部署的应用程序名或者是当前的项目名称比如我的项目名称是demo1在浏览器中输入为http://localhost:8080/demo1/a.jsp ${pageContext.request.contextPath}或<%=request.getContextPath()%>取出来的就是/demo1,而"/"代表的含义就是http://localhost:8080故有时候项目中这样写${pageContext.request.contextPath}/a.jsp绝对路径与相对路径的比较1)采用相对路径遇到的问题             相对路径固然比较灵活,但如果想复制页面内的代码却变得比较困难,因为不同的页面具有不同的相对路径,复制后必须修改每一个连接的路径。 如果页面被多于一个的页面所包含,那么被包含页面中的相对路径将是不正确的。 如果采用Struts的Action返回页面,那么由于页面路径与 Action路径不同,使得浏览器无法正确解释页面中的路径,如页面为/pages/cust/cust.jsp,图片所有目录为/images /title.gif,这时在/pages/cust/cust.jsp中的所用的路径为”http://images.cnblogs.com /title.gif”,但是如果某一个Action的Forward指向这个JSP文件,而这个Action的路径为/cust/manage.do, 那么页面内容中”http://images.cnblogs.com/title.gif”就不再指向正确的路径了。 解决以上问题似乎只有使用绝对路径了。2)采用绝对路径遇到的问题  随 着不同的Web应用发布方式,绝对路径的值也不同。如Web应用发布为MyApp,则路径”/MyApp/images/title.gif”是正确的, 但发布为另一应用时如MyApp2,这个路径就不对了,也许这个情况比较少,但以default方式发布Web应用时以上绝对路径也不 同:”/images/title.gif”。 二.解决方案 1)采用绝对路径,但为了解决不同部署方式的差别,在所有非struts标签的路径前加${pageContext.request.contextPath},如原路径为: ”/images/title.gif”,改为 “${pageContext.request.contextPath}/images/title.gif”。 代码” ${pageContext.request.contextPath}”的作用是取出部署的应用程序名,这样不管如何部署,所用路径都是正确的。缺点: 操作不便,其他工具无法正确解释${pageContext.request.contextPath} 2) 采用相对路径,在每个JSP文件中加入base标签,如: <base href="http://${header['host']}${pageContext.request.contextPath}/pages/cust/relation.jsp" /> 这样所有的路径都可以使用相对路径。缺点: 对于被包含的文件依然无效。     真正使用时需要灵活应用1)和2),写出更加健壮的代码。 在使用的时候可以使 用${pageContext.request.contextPath},也同时可以使 用<%=request.getContextPath()%>达到同样的效果,同时,也可以 将${pageContext.request.contextPath},放入一个JSP文件中,将用C:set放入一个变量中,然后在用的时候用EL 表达式取出来。 原文链接:https://blog.csdn.net/sky198989/article/details/84542160
  • [技术干货] ${pageContext.request.contextPath}的理解和用法
    ${pageContext.request.contextPath}是JSP取得绝对路径的方法,等价于<%=request.getContextPath()%> 。 也就是取出部署的应用程序名或者是当前的项目名称。 比如我的项目名称是SSM在浏览器中输入为 http://localhost:8080/SSM/index.jsp ${pageContext.request.contextPath}或<%=request.getContextPath()%>取出来的就是/SSM, 而"/"代表的含义就是 http://localhost:8080 可以使用el表达式,方法一: <c:set var="basePath"  value="${pageContext.request.contextPath}"/> 然后获取这个值: ${basePath} 可以使用el表达式,方法二: <c:set var="basePath"  value="<%=request.getContextPath()%>"/> 然后获取这个值: ${basePath} 把绝对路径的值以key-value 的形式存在请求范围,可以直接使用{basePath}取出 ———————————————— 原文链接:https://blog.csdn.net/weixin_43802688/article/details/89532658 
  • [技术干货] BCryptPasswordEncoder的使用及原理
           代码很长,但是真正关键的代码在上面第 43 、44 和 51 行的位置处,43 行处获取真正的 salt ,44 行是使用 base64 进行解码,然后 51 行用 密码、salt 进行处理。在来看看返回值是 rs,在第 63 行和 64 行,对 salt 进行 base64 编码后放入了 rs 中,然后对 hashed 进行 base64 编码后也放入了 rs 中,最后 rs.toString() 返回。          虽然上面代码很长,其实真正关键的就只有上面我提到的几句,其余的部分不用看。我们接着看 matches 的源码,同样单步进入,代码如下:  @Override public boolean matches(CharSequence rawPassword, String encodedPassword) {   if (rawPassword == null) {     throw new IllegalArgumentException("rawPassword cannot be null");   }   if (encodedPassword == null || encodedPassword.length() == 0) {     this.logger.warn("Empty encoded password");     return false;   }   if (!this.BCRYPT_PATTERN.matcher(encodedPassword).matches()) {     this.logger.warn("Encoded password does not look like BCrypt");     return false;   }   return BCrypt.checkpw(rawPassword.toString(), encodedPassword); }         单步到第 14 行的 return 处,这里调用 BCrypt.checkpw 的方法,rawPassword.toString() 是我们的密码,即 ”123456“, 后面的 encodePassword 是我们加密后的密码,单步步入进去,代码如下:  public static boolean checkpw(String plaintext, String hashed) {   return equalsNoEarlyReturn(hashed, hashpw(plaintext, hashed)); }         这里只有一行代码,但是代码中同样调用了前面的 hashpw 这个方法,传入的参数是 plaintext 和 hashed,plaintext 是我们的密码,即 “123456”, hashed 是加密后的密码。到这里基本就明白了。hashed 在进入 hashpw 函数后,会通过前面说到第 43 行代码取出真正的 salt,然后对通过 salt 和 我们的密码进行加密,这样流程就串联起来了。 总结        当时看到使用 BCryptPasswordEncoder 时,同样的密码可以生成不同的 密文 而且还可以通过 matches 方法进行匹配验证,觉得很神奇。后来经过调试发现,密文中本身包含了很多信息,包括 salt 和 使用 salt 加密后的 hash。因为每次的 salt 不同,因此每次的 hash 也不同。这样就可以使得相同的 明文 生成不同的 密文,而密文中包含 salt 和 hash,因此验证过程和生成过程也是相同的。 ———————————————— 原文链接:https://blog.csdn.net/easysec/article/details/121719949 
  • [技术干货] BCryptPasswordEncoder加密
    一. BCryptPasswordEncoder介绍 BCryptPasswordEncoder是Spring Security中的一个加密方法。BCryptPasswordEncoder方法采用了SHA-256+随机盐+密钥对密码进行加密。 SHA:安全Hash函数(SHA)是使用最广泛的Hash函数 加密算法与hash算法的区别: 加密算法是可逆的,加密算法的基本过程是对原来为明文的数据按某种算法进行处理,使其成为不可读的一段代码为“密文”,但在用相应的密钥进行操作之后就可以得到原来的内容 。 hash算法是一种单向密码体制,即它是一个从明文到密文的不可逆的映射,只有加密过程,没有解密过程。同时,哈希函数可以将任意长度的输入经过变化以后得到固定长度的输出。 二.BCryptPasswordEncoder中的方法: encode-加密:在BCryptPasswordEncoder中使用encode方法对密码进行加密,因为是通过hash算法进行加密,同样的密码输出的密文是不同的 matches-解密/匹配: 虽然通过encode方法加密的密码是不能解密的,但是在BCryptPasswordEncoder中提供了一个matches方法来匹配密码,它的原理是把需要配对的密码经过同一个hash函数计算,把计算得到的hash值到数据库中匹配,相同的hash值则说明是同一个密码。 ———————————————— 原文链接:https://blog.csdn.net/superxmh/article/details/118497296 
  • [技术干货] mybatis-plus中wrapper的用法
    用到了wrapper,ge、le、ne、eq等的用法,及多表查询自写sql整理资料记录一下,以备后续复习。 目录------------(可点击相应目录直接跳转) 一、条件构造器关系介绍 条件构造器关系介绍 : wapper介绍 : 二、项目实例 1、根据主键或者简单的查询条件进行查询 2、MyBatis-Plus还提供了Wrapper条件构造器,具体使用看如下代码: 三、具体使用操作 1、ge、gt、le、lt、isNull、isNotNull 2、eq、ne 3、between、notBetween 4、allEq 5、like、notLike、likeLeft、likeRight 6、in、notIn、inSql、notinSql、exists、notExists 7、or、and 8、嵌套or、嵌套and 9、orderBy、orderByDesc、orderByAsc 10、last 11、指定要查询的列 12、set、setSql 四、项目中实际应用代码实例 实例1--包含 eq相等的比较方法  实例2--包含 ge le ge等比较方法,及分页查询方法  实例3--多表查询,手写sql示例,五表联查 先了解一下内外连接: SQL内连接(INNER JOIN) SQL外连接(OUTER JOIN 一、条件构造器关系介绍 条件构造器关系介绍 : 上图绿色框为抽象类abstract 蓝色框为正常class类,可new对象 黄色箭头指向为父子类关系,箭头指向为父类 wapper介绍 : Wrapper : 条件构造抽象类,最顶端父类 AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件 QueryWrapper : Entity 对象封装操作类,不是用lambda语法 UpdateWrapper : Update 条件封装,用于Entity对象更新操作 AbstractLambdaWrapper : Lambda 语法使用 Wrapper统一处理解析 lambda 获取 column。 LambdaQueryWrapper :看名称也能明白就是用于Lambda语法使用的查询Wrapper  LambdaUpdateWrapper : Lambda 更新封装Wrapper 二、项目实例 1、根据主键或者简单的查询条件进行查询     /**      * 通过单个ID主键进行查询      */     @Test     public void selectById() {         User user = userMapper.selectById(1094592041087729666L);         System.out.println(user);     }       /**      * 通过多个ID主键查询      */     @Test     public void selectByList() {         List<Long> longs = Arrays.asList(1094592041087729666L, 1094590409767661570L);         List<User> users = userMapper.selectBatchIds(longs);         users.forEach(System.out::println);     }       /**      * 通过Map参数进行查询      */     @Test     public void selectByMap() {         Map<String, Object> params = new HashMap<>();         params.put("name", "张雨琪");         List<User> users = userMapper.selectByMap(params);         users.forEach(System.out::println);     }  2、MyBatis-Plus还提供了Wrapper条件构造器,具体使用看如下代码: /**      * 名字包含雨并且年龄小于40      * <p>      * WHERE name LIKE '%雨%' AND age < 40      */     @Test     public void selectByWrapperOne() {         QueryWrapper<User> wrapper = new QueryWrapper();         wrapper.like("name", "雨").lt("age", 40);         List<User> users = userMapper.selectList(wrapper);         users.forEach(System.out::println);     }       /**      * 名字包含雨      * 年龄大于20小于40      * 邮箱不能为空      * <p>      * WHERE name LIKE '%雨%' AND age BETWEEN 20 AND 40 AND email IS NOT NULL      */     @Test     public void selectByWrapperTwo() {         QueryWrapper<User> wrapper = Wrappers.query();         wrapper.like("name", "雨").between("age", 20, 40).isNotNull("email");         List<User> users = userMapper.selectList(wrapper);         users.forEach(System.out::println);     }       /**      * 名字为王性      * 或者年龄大于等于25      * 按照年龄降序排序,年龄相同按照id升序排序      * <p>      * WHERE name LIKE '王%' OR age >= 25 ORDER BY age DESC , id ASC      */     @Test     public void selectByWrapperThree() {         QueryWrapper<User> wrapper = Wrappers.query();         wrapper.likeRight("name", "王").or()                 .ge("age", 25).orderByDesc("age").orderByAsc("id");         List<User> users = userMapper.selectList(wrapper);         users.forEach(System.out::println);     }       /**      * 查询创建时间为2019年2月14      * 并且上级领导姓王      * <p>      * WHERE date_format(create_time,'%Y-%m-%d') = '2019-02-14' AND manager_id IN (select id from user where name like '王%')      */     @Test     public void selectByWrapperFour() {         QueryWrapper<User> wrapper = Wrappers.query();         wrapper.apply("date_format(create_time,'%Y-%m-%d') = {0}", "2019-02-14")                 .inSql("manager_id", "select id from user where name like '王%'");         List<User> users = userMapper.selectList(wrapper);         users.forEach(System.out::println);     }       /**      * 查询王姓      * 并且年龄小于40或者邮箱不为空      * <p>      * WHERE name LIKE '王%' AND ( age < 40 OR email IS NOT NULL )      */     @Test     public void selectByWrapperFive() {         QueryWrapper<User> wrapper = Wrappers.query();         wrapper.likeRight("name", "王").and(qw -> qw.lt("age", 40).or().isNotNull("email"));         List<User> users = userMapper.selectList(wrapper);         users.forEach(System.out::println);     }       /**      * 查询王姓      * 并且年龄大于20 、年龄小于40、邮箱不能为空      * <p>      * WHERE name LIKE ? OR ( age BETWEEN ? AND ? AND email IS NOT NULL )      */     @Test     public void selectByWrapperSix() {         QueryWrapper<User> wrapper = Wrappers.query();         wrapper.likeRight("name", "王").or(                 qw -> qw.between("age", 20, 40).isNotNull("email")         );         List<User> users = userMapper.selectList(wrapper);         users.forEach(System.out::println);     }       /**      * (年龄小于40或者邮箱不为空) 并且名字姓王      * WHERE ( age < 40 OR email IS NOT NULL ) AND name LIKE '王%'      */     @Test     public void selectByWrapperSeven() {         QueryWrapper<User> wrapper = Wrappers.query();         wrapper.nested(qw -> qw.lt("age", 40).or().isNotNull("email"))                 .likeRight("name", "王");         List<User> users = userMapper.selectList(wrapper);         users.forEach(System.out::println);     }       /**      * 查询年龄为30、31、32      * WHERE age IN (?,?,?)      */     @Test     public void selectByWrapperEight() {         QueryWrapper<User> wrapper = Wrappers.query();         wrapper.in("age", Arrays.asList(30, 31, 32));         List<User> users = userMapper.selectList(wrapper);         users.forEach(System.out::println);     }       /**      * 查询一条数据      * limit 1      */     @Test     public void selectByWrapperNine() {         QueryWrapper<User> wrapper = Wrappers.query();         wrapper.in("age", Arrays.asList(30, 31, 32)).last("limit 1");         List<User> users = userMapper.selectList(wrapper);         users.forEach(System.out::println);     }  三、具体使用操作 注意:以下条件构造器的方法入参中的 column 均表示数据库字段  1、ge、gt、le、lt、isNull、isNotNull   @Test public void testDelete() {       QueryWrapper<User> queryWrapper = new QueryWrapper<>();     queryWrapper         .isNull("name")         .ge("age", 12)         .isNotNull("email");     int result = userMapper.delete(queryWrapper);     System.out.println("delete return count = " + result); }  SQL:UPDATE user SET deleted=1 WHERE deleted=0 AND name IS NULL AND age >= ? AND email IS NOT NULL  2、eq、ne 注意:seletOne返回的是一条实体记录,当出现多条时会报错  @Test public void testSelectOne() {     QueryWrapper<User> queryWrapper = new QueryWrapper<>();     queryWrapper.eq("name", "Tom");       User user = userMapper.selectOne(queryWrapper);     System.out.println(user); }  3、between、notBetween 包含大小边界  @Test public void testSelectCount() {       QueryWrapper<User> queryWrapper = new QueryWrapper<>();     queryWrapper.between("age", 20, 30);       Integer count = userMapper.selectCount(queryWrapper);     System.out.println(count); } SELECT COUNT(1) FROM user WHERE deleted=0 AND age BETWEEN ? AND ?   4、allEq @Test public void testSelectList() {       QueryWrapper<User> queryWrapper = new QueryWrapper<>();     Map<String, Object> map = new HashMap<>();     map.put("id", 2);     map.put("name", "Jack");     map.put("age", 20);9       queryWrapper.allEq(map);     List<User> users = userMapper.selectList(queryWrapper);       users.forEach(System.out::println); } SELECT id,name,age,email,create_time,update_time,deleted,version FROM user WHERE deleted=0 AND name = ? AND id = ? AND age = ?   5、like、notLike、likeLeft、likeRight selectMaps返回Map集合列表  @Test public void testSelectMaps() {       QueryWrapper<User> queryWrapper = new QueryWrapper<>();     queryWrapper         .notLike("name", "e")         .likeRight("email", "t");       List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);//返回值是Map列表     maps.forEach(System.out::println); } SELECT id,name,age,email,create_time,update_time,deleted,version FROM user WHERE deleted=0 AND name NOT LIKE ? AND email LIKE ?   6、in、notIn、inSql、notinSql、exists、notExists in、notIn:  notIn("age",{1,2,3})--->age not in (1,2,3) notIn("age", 1, 2, 3)--->age not in (1,2,3) inSql、notinSql:可以实现子查询  例: inSql("age", "1,2,3,4,5,6")--->age in (1,2,3,4,5,6) 例: inSql("id", "select id from table where id < 3")--->id in (select id from table where id < 3) @Test public void testSelectObjs() {       QueryWrapper<User> queryWrapper = new QueryWrapper<>();     //queryWrapper.in("id", 1, 2, 3);     queryWrapper.inSql("id", "select id from user where id < 3");       List<Object> objects = userMapper.selectObjs(queryWrapper);//返回值是Object列表     objects.forEach(System.out::println); } SELECT id,name,age,email,create_time,update_time,deleted,version FROM user WHERE deleted=0 AND id IN (select id from user where id < 3)   7、or、and 注意:这里使用的是 UpdateWrapper 不调用or则默认为使用 and 连 @Test public void testUpdate1() {       //修改值     User user = new User();     user.setAge(99);     user.setName("Andy");       //修改条件     UpdateWrapper<User> userUpdateWrapper = new UpdateWrapper<>();     userUpdateWrapper         .like("name", "h")         .or()         .between("age", 20, 30);       int result = userMapper.update(user, userUpdateWrapper);     System.out.println(result); }  UPDATE user SET name=?, age=?, update_time=? WHERE deleted=0 AND name LIKE ? OR age BETWEEN ? AND ?  8、嵌套or、嵌套and 这里使用了lambda表达式,or中的表达式最后翻译成sql时会被加上圆括号 @Test public void testUpdate2() {       //修改值     User user = new User();     user.setAge(99);     user.setName("Andy");       //修改条件     UpdateWrapper<User> userUpdateWrapper = new UpdateWrapper<>();     userUpdateWrapper         .like("name", "h")         .or(i -> i.eq("name", "李白").ne("age", 20));       int result = userMapper.update(user, userUpdateWrapper);     System.out.println(result); } UPDATE user SET name=?, age=?, update_time=?  WHERE deleted=0 AND name LIKE ?  OR ( name = ? AND age <> ? )  9、orderBy、orderByDesc、orderByAsc @Test public void testSelectListOrderBy() {       QueryWrapper<User> queryWrapper = new QueryWrapper<>();     queryWrapper.orderByDesc("id");       List<User> users = userMapper.selectList(queryWrapper);     users.forEach(System.out::println); } SELECT id,name,age,email,create_time,update_time,deleted,version  FROM user WHERE deleted=0 ORDER BY id DESC 10、last 直接拼接到 sql 的最后 注意:只能调用一次,多次调用以最后一次为准 有sql注入的风险,请谨慎使用 @Test public void testSelectListLast() {     QueryWrapper<User> queryWrapper = new QueryWrapper<>();     queryWrapper.last("limit 1");       List<User> users = userMapper.selectList(queryWrapper);     users.forEach(System.out::println); }  SELECT id,name,age,email,create_time,update_time,deleted,version  FROM user WHERE deleted=0 limit 1 11、指定要查询的列 @Test public void testSelectListColumn() {       QueryWrapper<User> queryWrapper = new QueryWrapper<>();     queryWrapper.select("id", "name", "age");       List<User> users = userMapper.selectList(queryWrapper);     users.forEach(System.out::println); } SELECT id,name,age FROM user WHERE deleted=0 12、set、setSql 最终的sql会合并 user.setAge(),以及 userUpdateWrapper.set()  和 setSql() 中 的字段 @Test public void testUpdateSet() {     //修改值     User user = new User();     user.setAge(99);       //修改条件     UpdateWrapper<User> userUpdateWrapper = new UpdateWrapper<>();     userUpdateWrapper         .like("name", "h")         .set("name", "老李头")//除了可以查询还可以使用set设置修改的字段         .setSql(" email = '123@qq.com'");//可以有子查询     int result = userMapper.update(user, userUpdateWrapper); }  UPDATE user SET age=?, update_time=?, name=?, email = '123@qq.com' WHERE deleted=0 AND name LIKE ? 四、项目中实际应用代码实例 (此部分更新于2022年7月20日) 实例1--包含 eq相等的比较方法  实例2--包含 ge le ge等比较方法,及分页查询方法  实例3--多表查询,手写sql示例,五表联查 先了解一下内外连接: 什么是连接表? 多表查询原理:将多个表通过笛卡尔积形成一个虚表,再根据查询条件筛选符合条件的数据。 在关系数据库中,数据分布在多个逻辑表中。 要获得完整有意义的数据集,需要使用连接来查询这些表 中的数据。 SQL Server支持多种 连接包括 INNER JOIN:内连接,关键字在表中存在至少一个匹配时返回行。 left join : 左连接,返回左表中所有的记录以及右表中连接字段相等的记录。 right join : 右连接,返回右表中所有的记录以及左表中连接字段相等的记录。 inner join : 内连接,又叫等值连接,只返回两个表中连接字段相等的行。 full join : 外连接,返回两个表中的行:left join + right join。 cross join : 结果是笛卡尔积,就是第一个表的行数乘以第二个表的行数。 GROUP BY:全外连接, 子句必须放在 WHERE 子句中的条件之后,必须放在 ORDER BY 子句之前 每种连接类型指定SQL Server如何使用一个表中的数据来选择另一个表中的行 SQL内连接(INNER JOIN)   返回两张表中符合连接条件的数据行    内连接是从结果表中删除与被连接表中未匹配行的所有行,所以内连接可能会丢失信息 SQL外连接(OUTER JOIN)     外连接(OUTER JOIN)分 为左连接、右连接和全连接    左连接:返回左表中的所以行,如果左表中行在右表中没有匹配行,则结果中右表中的列返回空值NULL        语法:SELECT * FROM 表1 LEFT OUTER JOIN 表2 ON 条件     eg:我们左连接Student表、Score表查询学生的成绩,SQL 语句如下:     SELECT * FROM Student LEFT OUTER JOIN Score ON Student.id = Score.studentID     右 连 接:返回右表中的所以行,如果右表中行在左表中没有匹配行,则结果中左表中的列返回空值NULL      语法:SELECT * FROM 表1 RIGHT OUTER JOIN 表2 ON 条件     eg:我们右连接Student表、Score表查询学生的成绩,SQL 语句如下:     SELECT * FROM Student RIGHT OUTER JOIN Score ON Student.id = Score.studentID      全连接:返回左表和右表中的所有行,当某行在另一表中没有匹配行,则另一表中的补NULL ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------  这个是mybatis-plus插件中mapper的一个表查询写法,由多个表内连接或外连接组成的数据。 用到的表结构如下: 分别为问题库表、数据字典、数据字典类型、部门表、用户表 数据示例如下:obdis_problem表里去查type字段内容并返回对应的汉字语义 查询每个字段都进行一次表连接,拿到对应的值,得出一个表结果值,即A\B\C\D等等结果,最后拼起来返回前端展示即可。搞懂了这个,所有的多表查询就基本迎刃而解。 <!-- 问题库列表返回与查询-->     <select id="searchMoreProblem" resultType="com.hollysys.obdis.vo.problem.SearchObdisProblem">       select         p.id ,         p.level,         A.dict_value as level_text,         p.dept_id_mgr,         B.org_name as deptIdMgr_text,         p.dept_id_work,         C.org_name as deptIdWork_text,         p.station,         D.dict_value as station_text,         p.source,         E.dict_value as source_text,         p.categroy,         F.dict_value as categroy_text,         p.type,         G.dict_value as type_text,         p.dev_code,         H.dict_value as devCode_text,         p.check_time,         p.check_user,         I.username as checkUser_text,         p.deadline,         p.deadline_real,         p.duty_user,         J.username as dutyUser_text,         p.correct_user,         K.username as correctUser_text,         p.state,         p.problem,         p.requirement,         p.solution         from         obdis_problem   p         left join t_dict A on p.level = A.id         left join t_dept B on p.dept_id_mgr = B.org_id         left join t_dept C on p.dept_id_work = C.org_id         left join t_dict D on p.station = D.id         left join t_dict E on p.source = E.id         left join t_dict F on p.categroy = F.id         left join t_dict G on p.type = G.id         left join t_dict H on p.dev_code = H.id         left join t_user I on p.check_user = I.user_id         left join t_user J on p.duty_user = J.user_id         left join t_user K on p.correct_user = K.user_id       where 1=1       <if test="searchObdisProblem.levelText != null and searchObdisProblem.levelText != ''">        and A.dict_value like concat('%', #{searchObdisProblem.levelText}, '%')       </if>       <if test="searchObdisProblem.deptIdMgrText != null and searchObdisProblem.deptIdMgrText != ''">        and B.org_name like concat('%', #{searchObdisProblem.deptIdMgrText}, '%')       </if>       <if test="searchObdisProblem.deptIdWorkText != null and searchObdisProblem.deptIdWorkText != '' ">        and C.org_name like concat('%', #{searchObdisProblem.deptIdWorkText}, '%')       </if>       <if test="searchObdisProblem.stationText != null and searchObdisProblem.stationText != ''">         and D.dict_value = #{searchObdisProblem.stationText}       </if>       <if test="searchObdisProblem.sourceText != null and searchObdisProblem.sourceText != ''">        and E.dict_value = #{searchObdisProblem.sourceText}       </if>       <if test="searchObdisProblem.categroyText != null  and searchObdisProblem.categroyText != ''">         and F.dict_value = #{searchObdisProblem.categroyText}       </if>       <if test="searchObdisProblem.typeText != null  and searchObdisProblem.typeText != ''">         and G.dict_value = #{searchObdisProblem.typeText}       </if>       <if test="searchObdisProblem.devCodeText != null and searchObdisProblem.devCodeText != ''">         and H.dict_value = #{searchObdisProblem.devCodeText}       </if>       <if test="searchObdisProblem.checkTimeStart != null ">         and p.check_time &gt;= #{searchObdisProblem.checkTimeStart}       </if>         <if test="searchObdisProblem.checkTimeEnd != null ">             and p.check_time &lt;= #{searchObdisProblem.checkTimeEnd}             order by p.check_time         </if>       <if test="searchObdisProblem.checkUserText != null and searchObdisProblem.checkUserText != ''">         and I.username = #{searchObdisProblem.checkUserText}       </if>       <if test="searchObdisProblem.deadline != null ">         and p.deadline = #{searchObdisProblem.deadline}       </if>       <if test="searchObdisProblem.deadlineReal != null ">         and p.deadline_real = #{searchObdisProblem.deadlineReal}       </if>       <if test="searchObdisProblem.dutyUserText != null and searchObdisProblem.dutyUserText != ''">         and J.username = #{searchObdisProblem.dutyUserText}       </if>       <if test="searchObdisProblem.correctUserText != null and searchObdisProblem.correctUserText != ''">         and K.username = #{searchObdisProblem.correctUserText}       </if>       <if test="searchObdisProblem.state != null and searchObdisProblem.state != ''">         and state = #{searchObdisProblem.state}       </if>       <if test="searchObdisProblem.problem != null and searchObdisProblem.problem != ''">           and p.problem like concat('%', #{searchObdisProblem.problem}, '%')       </if>       <if test="searchObdisProblem.requirement != null and searchObdisProblem.requirement != ''">           and p.requirement like concat('%', #{searchObdisProblem.requirement}, '%')       </if>       <if test="searchObdisProblem.solution != null and searchObdisProblem.solution != ''">           and p.solution like concat('%', #{searchObdisProblem.solution}, '%')       </if>   </select>  -------------------------------------------------------------------------------------以下无正文------------------- 参考文档 1、(五)springboot + mybatis plus强大的条件构造器queryWrapper、updateWrapper_青蛙与大鹅的博客-CSDN博客_querywrapper.eq 2、https://blog.csdn.net/kepengs/article/details/112345870 3、Wrapper使用_weixin_39615889的博客-CSDN博客_wrapper使用 4、MyBatis-Plus Wrapper条件构造器查询大全_IT贱男的博客-CSDN博客_wrapper.like 5、https://blog.csdn.net/qq_48209375/article/details/114446611 6、https://blog.csdn.net/TBDBTUO/article/details/123550498 ———————————————— 原文链接:https://blog.csdn.net/qq_39715000/article/details/120090033 
  • [技术干货] JdbcTemplate详解
    java简介Java是一门面向对象的编程语言,不仅吸收了C++语言的各种优点,还摒弃了C++里难以理解的多继承、指针等概念,因此Java语言具有功能强大和简单易用两个特征。Java语言作为静态面向对象编程语言的代表,极好地实现了面向对象理论,允许程序员以优雅的思维方式进行复杂的编程。 Java具有简单性、面向对象、分布式、健壮性、安全性、独立与可移植性、多线程、动态性等特点 。Java可以编写桌面应用程序、Web应用程序、分布式系统和嵌入式系统应用程序等  。JDK称为Java开发包或Java开发工具,是一个编写Java的Applet小程序和应用程序的程序开发环境。JDK是整个Java的核心,包括了Java运行环境一些Java工具和Java的核心类库(Java API)。不论什么Java应用服务器实质都是内置了某个版本的JDK。主流的JDK是Sun公司发布的JDK,除了Sun之外,还有很多公司和组织都开发了自己的JDK,例如,IBM公司开发的JDK,BEA公司的Jrocket,还有GNU组织开发的JDK。 1.JdbcTemplate简介 JdbcTemplate是spring框架中提供的一个对象,是对原始繁琐的Jdbc API对象的简单封装。spring框架为我们提供了很多的操作模板类。例如:操作关系型数据的JdbcTemplate和HibernateTemplate,操作nosql数据库的RedisTemplate,操作消息队列的JmsTemplate等等。 JdbcTemplate是Spring JDBC的核心类,借助该类提供的方法可以很方便的实现数据的增删改查。 Spring对数据库的操作在jdbc上面做了深层次的封装,使用spring的注入功能,可以把DataSource注册到JdbcTemplate之中。 JdbcTemplate位于 spring-jdbc-4.3.0.RELEASE.jar 中。其全限定命名为org.springframework.jdbc.core.JdbcTemplate。要使用JdbcTemlate还需一个 spring-tx-4.3.0.RELEASE.jar 这个包包含了事务和异常控制 2.JdbcTemplate主要方法: JdbcTemplate主要提供以下五类方法: execute方法:可以用于执行任何SQL语句,一般用于执行DDL语句; update方法及batchUpdate方法:update方法用于执行新增、修改、删除等语句;batchUpdate方法用于执行批处理相关语句; query方法及queryForXXX方法:用于执行查询相关语句; call方法:用于执行存储过程、函数相关语句。 xml中的配置: <!-- 扫描 --> <context:component-scan base-package="com.xxx.*"></context:component-scan> <!-- 不属于自己工程的对象用bean来配置 --> <!-- 配置数据库连接池 --> <bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close" p:username="root" p:password="123456">     <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>     <property name="jdbcUrl" value="jdbc:mysql://127.0.0.1:3306/test"></property> </bean>  <!-- 配置jdbcTemplate --> <bean class="org.springframework.jdbc.core.JdbcTemplate" p:dataSource-ref="dataSource"></bean> 3.常用方法介绍 数据库user_info表: 先创建一个实体对象,对应数据库表中的信息,方便之后的查询操作  package com.xxx.vo; public class UserInfo {      private int id;     private String userName;     private String password;      public int getId() {         return id;     }      public void setId(int id) {         this.id = id;     }      public String getUserName() {         return userName;     }      public void setUserName(String userName) {         this.userName = userName;     }      public String getPassword() {         return password;     }      public void setPassword(String password) {         this.password = password;     }      @Override     public String toString() {         return "UserInfo [id=" + id + ", userName=" + userName + ", password=" + password + "]";     }      } update()方法增删改 修改(包含增、删、改):update()方法,另有批量插入方法batchUpdate() UserInfoDao.java代码: @Repository public class UserInfoDao {      @Autowired         //从容器中自动扫描获取jdbcTemplate     private JdbcTemplate jdbcTemplate;          //update()实现增加数据     public boolean insert(int id,String userName,String password){         String sql = "insert into user_info values (?,?,?)";         return jdbcTemplate.update(sql,id,userName,password)>0;     }          //update()实现修改     public boolean update(int id,String userName,String password){         String sql = "update user_info set user_name=?,password=? where id=?";         return jdbcTemplate.update(sql,userName,password,id)>0;     }           //update()实现删除     public boolean delete(int id){         String sql = "delete from user_info where id=?";         return jdbcTemplate.update(sql,id)>0;     }  } 测试类代码:  public class Test {      public static void main(String[] args) {                  ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");         UserInfoDao userDao = applicationContext.getBean(UserInfoDao.class);                  boolean insert = userDao.insert(1,"Jim", "123");         boolean update = userDao.update(1,"Tom","123456");         boolean delete = userDao.delete(1);      System.out.println("插入:"+insert+",修改:"+update+",删除:"+delete);         } } 测试结果:  query()查询方法 查询:查询单个值、查询一个对象、查询多个对象 查询单个值 //查询单个值 public boolean login(String userName,String password){     try {         String sql = "select id from user_info where user_name=? and password=?";         jdbcTemplate.queryForObject(sql,String.class,userName,password);         return true;     } catch (DataAccessException e) {         return false;     } } 查询一个对象:RowMapper方法和ResultSetExtractor方法  //查询单个对象 public UserInfo getById(int id){     String sql = "select id,user_name,password from user_info where id=?";          //RowMapper方法     class UserInfoRowMapper implements RowMapper<UserInfo>{          @Override         public UserInfo mapRow(ResultSet rs, int rowNum) throws SQLException {             UserInfo userInfo = new UserInfo();             userInfo.setId(rs.getInt("id"));             userInfo.setUserName(rs.getString("user_name"));             userInfo.setPassword(rs.getString("password"));             return userInfo;         }              }          return jdbcTemplate.queryForObject(sql,new UserInfoRowMapper(),id);      //RowMapper方法的Lambda表达式     return jdbcTemplate.queryForObject(sql,(ResultSet rs,int rowNum)->{         UserInfo userInfo = new UserInfo();         userInfo.setId(rs.getInt("id"));         userInfo.setUserName(rs.getString("user_name"));         userInfo.setPassword(rs.getString("password"));         return userInfo;     },id);          //ResultSetExtractor方法     class UserInfoResultSet implements ResultSetExtractor<UserInfo>{          @Override         public UserInfo extractData(ResultSet rs) throws SQLException, DataAccessException {             UserInfo userInfo = null;             if(rs.next()){                 userInfo = new UserInfo();                 userInfo.setId(rs.getInt("id"));                 userInfo.setUserName(rs.getString("user_name"));                 userInfo.setPassword(rs.getString("password"));             }             return userInfo;         }     }     return jdbcTemplate.query(sql,new UserInfoResultSet(),id);          //ResultSetExtractor方法的lambda表达式     return jdbcTemplate.query(sql,(ResultSet rs)->{         UserInfo userInfo = null;         if(rs.next()){             userInfo = new UserInfo();             userInfo.setId(rs.getInt("id"));             userInfo.setUserName(rs.getString("user_name"));             userInfo.setPassword(rs.getString("password"));         }         return userInfo;     },id);      } 查询多个对象:RowMapper方法和ResultSetExtractor方法  //查询多个对象 public List<UserInfo> selectAll(){     String sql = "select id,user_name,password from user_info";          //RowMapper方法     class UserInfoRowMapper implements RowMapper<UserInfo>{                  @Override         public UserInfo mapRow(ResultSet rs, int rowNum) throws SQLException {             UserInfo userInfo = new UserInfo();             userInfo.setId(rs.getInt("id"));             userInfo.setUserName(rs.getString("user_name"));             userInfo.setPassword(rs.getString("password"));             return userInfo;         }              }     return jdbcTemplate.query(sql,new UserInfoRowMapper());               //RowMapper方法的Lambda表达式     return jdbcTemplate.query(sql,(ResultSet rs,int rowNum)->{         UserInfo userInfo = new UserInfo();         userInfo.setId(rs.getInt("id"));         userInfo.setUserName(rs.getString("user_name"));         userInfo.setPassword(rs.getString("password"));         return userInfo;     });          //ResultSetExtractor方法     class UserInfoResultSet implements ResultSetExtractor<List<UserInfo>>{          @Override         public List<UserInfo> extractData(ResultSet rs) throws SQLException, DataAccessException {             List<UserInfo> list = new ArrayList<>();             while(rs.next()){                 UserInfo userInfo = new UserInfo();                 userInfo.setId(rs.getInt("id"));                 userInfo.setUserName(rs.getString("user_name"));                 userInfo.setPassword(rs.getString("password"));                 list.add(userInfo);             }             return list;         }              }     return jdbcTemplate.query(sql, new UserInfoResultSet());          //ResultSetExtractor方法的lambda表达式     return jdbcTemplate.query(sql,(ResultSet rs)->{         List<UserInfo> list = new ArrayList<>();         while(rs.next()){             UserInfo userInfo = new UserInfo();             userInfo.setId(rs.getInt("id"));             userInfo.setUserName(rs.getString("user_name"));             userInfo.setPassword(rs.getString("password"));             list.add(userInfo);         }         return list;     });      } ———————————————— 原文链接:https://blog.csdn.net/mfysss/article/details/129131075 
  • [技术干货] this.$toast() 了解一下
    前言在平时的开发过程中,我们总是先写好一个组件,然后在需要的页面中用 import 引入即可,但如果是下面这种类型的组件呢上面这种类型的浮层提示有一个很大的特点,就是使用频率特别高,几乎每个页面都会用到它,于是乎我们就要在每个页面中去引入该组件,并且在每个页面都得通过一个变量来控制它的显隐,这显然不是我们想要的。。。那我们想要的是什么样呢?用过一些 UI 框架的同学们应该知道有这样一种用法:this.$toast({ duration: 3000, content: '这是一条消息提示' }); 没错,就是这么简单的一句话就万事大吉了(就是用 js 调用组件而已啦)。那这种效果究竟是怎么实现的呢?今天就让我们来(手把手)一探究竟吧!前置知识不知道小伙伴们有没有用过 Vue.extend() 这个东东,反正我是很少碰过,印象不深,所以这里我们先来短暂了解一下 Vue.extend() 主要是用来干嘛的。先来个官方说明(不多的,坚持下): 没怎么看懂?没关系,不重要,你只要记住(加少许理解)以下用法即可:// 导入以往的普通组件 import Main from './main.vue'; // 用 Vue.extend 创建组件的模板(构造函数) let mainConstructor = Vue.extend(Main); // 实例化组件 let instance = new mainConstructor(); // 挂载到相应的元素上 instance.$mount('#app'); 不知道你看懂没有,上面的 Vue.extend(Main) 就是一个基于 main.vue 的组件模板(构造函数),instance 是实例化的组件,$mount() 是手动挂载的意思。其中 Vue.extend() 和 $mount() 就是我们通过 js 调用、渲染并挂载组件的精髓所在,相当于早前的 createElement 和 appendChild,有异曲同工之效。这个点需要我们好好熟悉一下,所以你可以先停下来屡屡思路。补充一下:$mount() 里面如果没有参数,说明组件只是渲染了但还没有挂载到页面上,如果有正确的(元素)参数则直接挂载到元素下面。写一个 toast 组件js 调用归调用,最原始的组件还是要有的,只是我们不通过 import 来引入到页面中而已。ok,我们就以最开始的那个 toast 图片来简单写一下这个 vue 组件(message 和 alert 也是一样的)。这里就直接上代码啦,毕竟它的结构简单到爆了,也不是本章节的重点:<!-- main.vue --> <template> <div class="toast"> <p>服务器错误,请稍后重试</p> </div> </template> <script> export default { name: "Toast", mounted() { setTimeout(() => { // 3s 后通过父级移除子元素的方式来移除该组件实例和 DOM 节点 this.$destroy(true); this.$el.parentNode.removeChild(this.$el); }, 3000); } }; </script> <style lang="scss" scoped> .toast { display: flex; align-items: center; justify-content: center; position: fixed; top: 0; bottom: 0; left: 0; right: 0; color: #fff; z-index: 9999; background: transparent; > p { padding: 12px 22px; font-size: 18px; border-radius: 4px; background: rgba(17, 17, 17, 0.7); } } </style> 上面的内容想必大家应该都能看懂,所以这里就直接讲下面的重点了。写一个 main.js我们在 main.vue 的同级目录下新建一个 main.js 文件。我们先瞟一眼文件内容(也不多,已经是个最简版了):// main.js import Vue from "vue"; // 引入 Vue 是因为要用到 Vue.extend() 这个方法 import Main from "./main.vue"; // 引入刚才的 toast 组件 let ToastConstructor = Vue.extend(Main); // 这个在前面的前置知识内容里面有讲到 let instance; const Toast = function() { instance = new ToastConstructor().$mount(); // 渲染组件 document.body.appendChild(instance.$el); // 挂载到 body 下 }; export default Toast; 上面的代码暴露了一个 Toast 函数。为什么要暴露一个函数呢?原因很简单:你想想,我们最终是不是要根据 this.$toast() 来调用一个组件,说白了,通过 js 调用,本质就是调用一个 函数。也就是说 this.$toast() 就是执行了上面代码中导出的 export default Toast,也就是执行了 Toast 函数(const Toast = function() {}),所以当我们调用 this.$toast() 的时候其实就是执行了 Toast() 函数。而 Toast() 函数只做了一件事情:就是通过手动挂载的方式把组件挂载到 body 下面。补充一下:一般来说我们常见的是 $mount("#app"),也就是把组件挂载到 #app 下面,<router-view /> 也包含在 #app 中,但是我们这种 toast 提示是放在 body 下面的,也就是说它不受 #app 和 <router-view /> 的管控,所以当我们切换页面(路由)的时候,这个 toast 组件是不会跟着立马消失的,这点要注意哦。这里顺便给个组件的目录结构,如下图所示: 开始调用调用方式很简单,首先我们在入口文件 main.js(和上面不是同一个) 里加上两行代码,这样我们就能在需要的地方直接用 js 调用它了,如下图所示:  然后在页面中测试一下,就像下面这样子: 运行一下代码: 嗯,挺好,小有成就的 feel 。支持可传参数别急,我们好像还漏了点什么。。。对了,现在还不支持传参呢,直接调用 this.$toast() 就只能显示————服务器错误,请稍后重试(这下全都是后端的锅了)。但我们可是个有追求的前端,不能局限于此,所以现在让我们来尝试增加下两个可配置参数,这里拿 duration 和 content 举个栗子。首先我们要修改 main.vue 组件里面的内容(其实没啥大变化),就像下面这样:<!-- main.vue 可配置版 --> <template> <div class="toast"> <p>{{ content }}</p> </div> </template> <script> // 主要就改了 data export default { name: "Toast", data() { return { content: "", duration: 3000 }; }, mounted() { setTimeout(() => { this.$destroy(true); this.$el.parentNode.removeChild(this.$el); }, this.duration); } }; </script> 上面的代码应该算是浅显易懂了,接下来我们看下 main.js 里面改了啥:// main.js 可配置版 import Vue from "vue"; import Main from "./main.vue"; let ToastConstructor = Vue.extend(Main); let instance; const Toast = function(options = {}) { // 就改了这里,加了个 options 参数 instance = new ToastConstructor({ data: options // 这里的 data 会传到 main.vue 组件中的 data 中,当然也可以写在 props 里 }); document.body.appendChild(instance.$mount().$el); }; export default Toast; 其实 main.js 也没多大变化,就是在函数里面加了个参数。要注意的是 new ToastConstructor({ data: options }) 中的 data 就是 main.vue 组件中的 data,不是随随便便取的字段名,传入的 options 会和组件中的 data 合并(Vue 的功劳)。em。。。是的,就这么简单,现在让我们继续来调用一下它:<script> export default { methods: { showToast() { this.$toast({ content: "哈哈哈哈,消失的贼快", duration: 500 }); } } }; </script> 运行一下就可以看到: 当然,这还没完,我们继续添加个小功能点。。。支持 this.$toast.error()这里我们打算支持 this.$toast.error() 和 this.$toast.success() 这两种方式,所以我们第一步还是要先去修改一下 main.vue 文件的内容(主要就是根据 type 值来修改组件的样式),就像下面这样:<!--main.vue--> <template> <div class="toast" :class="type ? `toast--${type}` : ''"> <p>{{ content }}</p> </div> </template> <script> export default { ... data() { return { type: "", content: "", duration: 3000 }; }, ... }; </script> <style lang="scss" scoped> .toast { ... &--error p { background: rgba(255, 0, 0, 0.5); } &--success p { background: rgba(0, 255, 0, 0.5); } } </style> 其次,this.$toast.error() 其实就等价于 Toast.error(),所以我们现在的目的就是要给 Toast 函数扩充方法,也比较简单,就先看代码再解释吧:// main.js const Toast = function(options = {}) { ... }; // 以下就是在 Toast 函数中拓展 ["success", "error"] 这两个方法 ["success", "error"].forEach(type => { Toast[type] = options => { options.type = type; return Toast(options); }; }); export default Toast; 我们可以看到 Toast.error() 和 Toast.success() 最终还是调用 Toast(options) 这个函数,只不过在调用之前需要多做一步处理,就是将 ["success", "error"] 作为一个 type 参数给合并进 options 里面再传递,仅此而已。那就试试效果吧:<script> export default { methods: { showToast() { this.$toast({ content: "这是正常的" }); }, showErrorToast() { this.$toast.error({ content: "竟然失败了" }); }, showSuccessToast() { this.$toast.success({ content: "居然成功了" }); } } }; </script> 结语至此,一个通过 js 调用的简单 toast 组件就搞定啦,短短的几行代码还是挺考验 js 功底的。当然这只是个超简易版的 demo,显然不够完善和健壮,所以我们可以在此基础上扩充一下,比如当 duration <= 0 的时候,我们让这个 toast 一直显示,然后扩展一个 close 方法来关闭等等之类的。不过还是那句老话,实践才是检验真理的唯一标准。纸上得来终觉浅,绝知此事要躬行。step by step, day day up !作者:尤水就下链接:https://juejin.cn/post/6844903825711562766
  • [技术干货] RPM包安装
    RPM包安装1、如何安装rpm软件包rmp软件包的安装可以使用程序rpm来完成。执行下面的命令rpm -i your-package.rpm其中your-package.rpm是你要安装的rpm包的文件名,一般置于当前目录下。安装过程中可能出现下面的警告或者提示:... conflict with ... 可能是要安装的包里有一些文件可能会覆盖现有的文件,缺省时这样的情况下是无法正确安装的可以用rpm --force -i 强制安装即可... is needed by ...... is not installed ... 此包需要的一些软件你没有安装可以用rpm --nodeps -i 来忽略此信息也就是说,rpm -i --force --nodeps 可以忽略所有依赖关系和文件问题,什么包都能安装上,但这种强制安装的软件包不能保证完全发挥功能2、如何安装.src.rpm软件包有些软件包是以.src.rpm结尾的,这类软件包是包含了源代码的rpm包,在安装时需要进行编译。这类软件包有两种安装方法,方法一:执行rpm -i your-package.src.rpmcd /usr/src/redhat/SPECSrpmbuild -bp your-package.specs 一个和你的软件包同名的specs文件cd /usr/src/redhat/BUILD/your-package/ 一个和你的软件包同名的目录./configure 这一步和编译普通的源码软件一样,可以加上参数makemake install方法二:执行rpm -i you-package.src.rpmcd /usr/src/redhat/SPECS 前两步和方法一相同rpmbuild -bb your-package.specs 一个和你的软件包同名的specs文件这时,在/usr/src/redhat/RPM/i386/ (根据具体包的不同,也可能是i686,noarch等等)目录下,有一个新的rpm包,这个是编译好的二进制文件,执行rpm -i new-package.rpm即可安装完成。3、如何卸载rpm软件包使用命令 rpm -e 包名,包名可以包含版本号等信息,但是不可以有后缀.rpm。比如,卸载软件包proftpd-1.2.8-1,可以使用下列格式:rpm -e proftpd-1.2.8-1 rpm -e proftpd-1.2.8 rpm -e proftpd- rpm -e proftpd不可以是下列格式:rpm -e proftpd-1.2.8-1.i386.rpm rpm -e proftpd-1.2.8-1.i386 rpm -e proftpd-1.2 rpm -e proftpd-1有时会出现一些错误或者警告:... is needed by ... 这说明这个软件被其他软件需要,不能随便卸载可以用rpm -e --nodeps强制卸载4、如何不安装但是获取rpm包中的文件使用工具rpm2cpio和cpiorpm2cpio xxx.rpm | cpio -vi rpm2cpio xxx.rpm | cpio -idmv rpm2cpio xxx.rpm | cpio --extract --make-directories参数i和extract相同,表示提取文件。v表示指示执行进程d和make-directory相同,表示根据包中文件原来的路径建立目录m表示保持文件的更新时间。5、如何查看与rpm包相关的文件和其他信息下面所有的例子都假设使用软件包mysql-3.23.54a-11我的系统中安装了那些rpm软件包rpm -qa 列出所有安装过的包如果要查找所有安装过的包含某个字符串sql的软件包rpm -qa |grep sql如何获得某个软件包的文件全名rpm -q mysql可以获得系统中安装的mysql软件包全名,从中可以获得当前软件包的版本等信息。这个例子中可以得到信息mysql-3.23.54a-11。一个rpm包中的文件安装到那里去了?rpm -ql 包名注意:这里的包名是不包括.rpm后缀的软件包的名称,也就是说只能用mysql或者mysql-3.23.54a-11而不是mysql-3.23.54a-11.rpm,如果只是想知道可执行程序放到那里去了,也可以用which,比如which mysql一个rpm包中包含那些文件一个没有安装过的软件包,使用:rpm -qlp ****.rpm一个已经安装过的软件包,还可以使用:rpm -ql ****.rpm如何获取关于一个软件包的版本,用途等相关信息?一个没有安装过的软件包,使用rpm -qip ****.rpm。一个已经安装过的软件包,还可以使用rpm -qi ****.rpm。某个程序是哪个软件包安装的,或者哪个软件包包含这个程序rpm -qf `which 程序名` 返回软件包的全名 rpm -qif `which 程序名` 返回软件包的有关信息 rpm -qlf `which 程序名` 返回软件包的文件列表注意,这里不是引号,而是`,就是键盘左上角的那个键,也可以使用rpm -qilf,同时输出软件包信息和文件列表。某个文件是哪个软件包安装的,或者哪个软件包包含这个文件注意,前一个问题中的方法,只适用与可执行的程序,而下面的方法,不仅可以用于可执行程序,也可以用于普通的任何文件,前提是知道这个文件名,首先获得这个程序的完整路径,可以用whereis或者which,然后使用rpm -qf例如:# whereis ftptop ftptop: /usr/bin/ftptop /usr/share/man/man1/ftptop.1.gz # rpm -qf /usr/bin/ftptop proftpd-1.2.8-1 # rpm -qf /usr/share/doc/proftpd-1.2.8/rfc/rfc0959.txt proftpd-1.2.8-1获得软件包相关的信息用rpm -q,q表示查询query,后面可以跟其他选项,比如i 表示info,获得软件包的信息; l 表示list,获得文件列表; a 表示all,在所有包中执行查询; f 表示file,根据文件进行相关的查询; p 表示package,根据软件包进行查询需要的查询条件可以使用grep产生,或者从""中的命令行产生原文链接: https://www.kancloud.cn/linux-tech/linux_learning/661736
  • [技术干货] 如何升级openssl
    在计算机网络上,OpenSSL是一个开放源代码的软件库包,应用程序可以使用这个包来进行安全通信,避免窃听,同时确认另一端连接者的身份。这个包广泛被应用在互联网的网页服务器上。SSL是Secure Sockets Layer(安全套接层协议)的缩写,可以在Internet上提供秘密性传输。Netscape公司在推出第一个Web浏览器的同时,提出了SSL协议标准。其目标是保证两个应用间通信的保密性和可靠性,可在服务器端和用户端同时实现支持。已经成为Internet上保密通讯的工业标准。 SSL能使用户/服务器应用之间的通信不被攻击者窃听,并且始终对服务器进行认证,还可选择对用户进行认证。SSL协议要求建立在可靠的传输层协议(TCP)之上。SSL协议的优势在于它是与应用层协议独立无关的,高层的应用层协议(例如:HTTP,FTP,TELNET等)能透明地建立于SSL协议之上。SSL协议在应用层协议通信之前就已经完成加密算法、通信密钥的协商及服务器认证工作。在此之后应用层协议所传送的数据都会被加密,从而保证通信的私密性。很多加密算法用到了openssl这个组件,低版本的存在很多漏洞和风险需要升级到新版本。基本功能 OpenSSL整个软件包大概可以分成三个主要的功能部分:SSL协议库、应用程序以及密码算法库。OpenSSL的目录结构自然也是围绕这三个功能部分进行规划的。 作为一个基于密码学的安全开发包,OpenSSL提供的功能相当强大和全面,囊括了主要的密码算法、常用的密钥和证书封装管理功能以及SSL协议,并提供了丰富的应用程序供测试或其它目的使用。 辅助功能 BIO机制是OpenSSL提供的一种高层IO接口,该接口封装了几乎所有类型的IO接口,如内存访问、文件访问以及Socket等。这使得代码的重用性大幅度提高,OpenSSL提供API的复杂性也降低了很多。 OpenSSL对于随机数的生成和管理也提供了一整套的解决方法和支持API函数。随机数的好坏是决定一个密钥是否安全的重要前提。 OpenSSL还提供了其它的一些辅助功能,如从口令生成密钥的API,证书签发和管理中的配置文件机制等等。如果你有足够的耐心,将会在深入使用OpenSSL的过程慢慢发现很多这样的小功能,让你不断有新的惊喜。工具/原料opensslcentos6.5_x64方法/步骤两种方法查看当前openssl版本源码编译之前要先安装zlib下载最新的源码包openssl-1.1.0c.tar.gztar zxvf openssl-1.1.0c.tar.gzcd openssl-1.1.0c./config shared zlib  --prefix=/usr/local/openssl && make && make install移除老版本的openssl,创建新的软连接mv /usr/bin/openssl /usr/bin/openssl.oldmv /usr/include/openssl /usr/include/openssl.oldln -s /usr/local/openssl/bin/openssl /usr/bin/opensslln -s /usr/local/openssl/include/openssl /usr/include/opensslln -sf /usr/local/openssl/lib/libcrypto.so.1.0.0 /lib/libcrypto.so.6echo "/usr/local/openssl/lib" >>/etc/ld.so.conf ldconfig -v6.至此openssl升级完成原文链接:https://jingyan.baidu.com/article/bea41d4386a9f2b4c51be6aa.html
总条数:764 到第
上滑加载中