API横向越权修复之ID加密

  • 横向越权

横向越权一般发生在应用系统做了【认证】,但没有做【鉴权】的情况下,也是最常见的漏洞之一。

  • 认证:即识别是否有权限访问系统;
  • 鉴权:即识别在系统中的权限是什么;

例如:

// 访问某数据查询接口,接口返回ID为123的数据信息POST : https://xxxx/iservice/queryInfo?detail_id=123 

请求接口时一般都会要求携带TOKEN,无论是JWT还是RSA的,至少不会是裸奔。这里的TOKEN就是【认证】信息,接口通过TOKEN去判断当前用户是否有请求接口的权限。但如果接口中没有做【鉴权】则会发生横向越权,用户通过修改detail_id的值就可以遍历DB中的所有记录

解决的思路:

  • 建立数据权限:常见的有:【RBAC:role based access control】, 基于角色的权限控制,一般用户不被直接授予权限,而是通过Role去获取权限。将数据的访问权限与角色绑定,用户拥有什么角色才能看到什么数据,这样即使遍历接口也只能查询到当前用户自己的数据;
  • ID加密:如例子中的detail_id,如果我们换成uuid,或其他无规则的值,也可以降低被遍历的可能性;

建立完善的权限策略是控制越权最合适的方法,但很多系统已经维护了很多年,里面的功能很庞大,往里面集成权限策略难度较大,需要去定义角色,梳理业务数据与角色的关系,然后开发权限管理功能,再挨个功能去添加鉴权;这里提供ID加密的方式去处理横向越权。

  • 目的:

      1、对原代码(业务)入侵小;2、降低数据遍历风险;3、投入人天小;
    
  • 代码实现思路

通过全局拦截API入参与返回值,对可遍历字段进行加解密。无需前端参与,后端返回数据时,对字段进行加密,加密算法保存在后端,前端使用加密字段进行后续业务处理,后端接口入参接收时进行解密。

序号业务字段1业务字段2业务字段3行ID【非业务字段,对用户不可见】
1xxxxxxxxxwMul8LwP =》 实际值:123
2xxxxxxxxx3vRRDk6X =》 实际值:124
3xxxxxxxxxTbxJ3IAe =》 实际值:125

用户选择查看序号1的行时,请求后端返回详细数据,接口如下:

https://xxxx/iservice/queryInfo?detail_id=wMul8LwP

此时如果要恶意遍历接口的话,难度相对较高,还可以将ID的加密强度提升来提供安全性。

  • 代码实现

以下均基于JAVA语言+springboot框架实现。通过反射,在拦截中判断字段是否有加密或解密注解,进行对应的加解密操作后流转。

  • 自定义注解
/*** 字段解密* @author lu*/
@Target({ElementType.FIELD,ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface Decrypt {}
	解密在接口入参中使用,一般为RO对象,或者是基础类型的参数,所以作用域为FIELD或PARAMETER
/*** 字段加密* @author lu*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Encrypt {}
	加密在返回值(VO)中使用,一般都为对象,所以注解作用域为FIELD;
  • 加密算法

    加密强度自己选择,这里以DES加密为例

/*** 加解密* @author lu*/
@Slf4j
public class DesUtil {public static final String SECURITY_KEY = "IxDQ4e5bCEY";public static String encrypt(String info) {byte[] key = new byte[0];try {key = new BASE64Decoder().decodeBuffer(SECURITY_KEY);} catch (IOException e) {log.error("加密失败",e);}DES des = SecureUtil.des(key);String encrypt = des.encryptHex(info);return encrypt;}public static String decode(String encrypt) {byte[] key = new byte[0];try {key = new BASE64Decoder().decodeBuffer(SECURITY_KEY);} catch (IOException e) {log.error("解密失败",e);}DES des = SecureUtil.des(key);return des.decryptStr(encrypt);}
}
  • 接口返回值加密

      responseBodyAdvice —— 响应体的统一处理器,一般用来统一返回值使用。这里用于返回值字段加密。
    
/*** 返回值字段加密* @author lu*/
@Slf4j
@RestControllerAdvice
public class ResponseEncryptAdvice implements ResponseBodyAdvice {/** 此处如果返回false , 则不执行当前Advice的业务 */@Overridepublic boolean supports(MethodParameter methodParameter, Class aClass) {return true;}/*** @title 写返回值前执行** */@Overridepublic Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {try {// 获取data类型Class clazz = body.getClass();// 是否是集合boolean isCollectionType = Collection.class.isAssignableFrom(clazz);if(isCollectionType){return encodeList(body);}else{return encode(body);}}catch (Exception e){log.error("请求后置处理异常",e);}return body;}/*** 递归加密*/private JSONObject encode(Object object) throws IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {// 获取data类型Class clazz = object.getClass();// 转成JSON处理,字段加密后数据类型会变,原类无法处理JSONObject jsonObject = (JSONObject) JSONObject.toJSON(object);// 递归遍历类里及父类所有属性,找到所有带加密注解的字段Field[] fields = FieldsUtils.getClassAllFields(clazz);for (Field field : fields) {// 获取字段值field.setAccessible(true);Object val = field.get(object);if(val==null){// 空值不处理continue;}// final 修饰的跳过!!!,避免出现递归死循环的问题,例如:PageInfoint modify = field.getModifiers();if(Modifier.isFinal(modify)){continue;}// 字段类型Class valClass = val.getClass();// 是否是集合boolean isCollectionType = Collection.class.isAssignableFrom(valClass);// 是否是对象,排除掉基础数据类型与包装类boolean isObject = isJsonObject(val);// 如果是带加密注解的字段,不管什么类型,直接转String加密if(field.isAnnotationPresent(Encrypt.class)){// 字段加密jsonObject.put(field.getName(), DesUtil.encrypt(val.toString()));}// 如果是集合类型else if(isCollectionType){JSONArray jsonArray = encodeList(val);jsonObject.put(field.getName(),jsonArray);}// 基础数据类型else if(!isObject){// 基础数据类型且没有注解,直接放过continue;}// 如果是自定义的类,则继续下沉找是否有加密字段else /*if (valClass.getPackage().getName().startsWith("com.lu.test"))*/{jsonObject.put(field.getName(),encode(val));}/*// 其他接口带泛型的,例如:Ipage , PageInfoelse if(valClass.equals(IPage.class)){// 调用获取行数据的方法Method method = valClass.getMethod("size");}else if(valClass.equals(PageInfo.class)){// 把PageInfo转成JSON处理JSONObject jsonObject = (JSONObject) JSONObject.toJSON(object);// 调用获取行数据的方法Method method = valClass.getMethod("getList");// 获取行数据Object rows = method.invoke(val);}*/}return jsonObject;}/*** 集合* @param object* @return*/private JSONArray encodeList(Object object) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, NoSuchFieldException {// 以JSONARRAY存储JSONArray jsonArray =  new JSONArray();Class clazz = object.getClass();// 遍历集合Method sizeMethod = clazz.getMethod("size");// 调用List的size()方法获取元素数量int size = (int)sizeMethod.invoke(object);// 获取元素Method toArrayMethod = clazz.getMethod("toArray");Object[] elementArr = (Object[]) toArrayMethod.invoke(object);for (int i = 0; i < size; i++) {// 获取元素属性//Field listField = clazz.getDeclaredField("elementData");// 设置访问权限//listField.setAccessible(true);// 获取元素Object element = elementArr[i];// 丢进去递归jsonArray.add(encode(element));}return jsonArray;}/*** 判断是否是JSON字符串* @param object* @return*/private static boolean isJsonObject(Object object) {try {JSONObject jsonObject = (JSONObject) JSONObject.toJSON(object);return true;} catch (Exception e) {return false;}}
}

适用于常见的返回值类型List<> ,Ipage , PageInfo, 以及自定义返回对象 。

  • 接口入参解密 (POST / JSON)

      入参的处理相对麻烦,因为参数的位置(contentType)多样性.
    
/*** 入参解密* @author lu*/
@Slf4j
@RestControllerAdvice
public class RequestJsonBodyDecryptAdvice implements RequestBodyAdvice {/** 此处如果返回false , 则不执行当前Advice的业务 */@Overridepublic boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {return true;}/*** @title 读取参数前执行* @description 在此做些编码 / 解密 / 封装参数为对象的操作** POST 请求 JSON格式入参会进入这里** */@Overridepublic HttpInputMessage beforeBodyRead(HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) throws IOException {try{// 当前接口方法Method method = methodParameter.getMethod();// 获取参数集合 @RequestBody 只有一个参数Parameter[] parameters = method.getParameters();if(ArrayUtils.isNotEmpty(parameters)){for (Parameter parameter : parameters) {if(parameter.isAnnotationPresent(RequestBody.class)){Class bodyType = parameter.getType();return  new DecryptHttpInputMessage(httpInputMessage,type,bodyType);}}}}catch (Exception e){log.error("请求参数解密失败",e);throw new BusinessException("请求参数错误!");}return httpInputMessage;}/*** @title 读取参数后执行* @author Xingbz*/@Overridepublic Object afterBodyRead(Object body, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {return body;}/*** @title 无请求参数时的处理*/@Overridepublic Object handleEmptyBody(Object body, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {return body;}/*** 解密-使用解密后的数据,构造新的读取流*/class DecryptHttpInputMessage implements HttpInputMessage {private HttpHeaders headers;private InputStream body;public DecryptHttpInputMessage(HttpInputMessage inputMessage, Type type,Class bodyType) throws Exception {// 转存请求头this.headers = inputMessage.getHeaders();// 请求JSONString bodyStr = StringUtils.defaultString(IOUtils.toString(inputMessage.getBody(), "UTF-8"));log.info("headers:{},body:{}",headers,bodyStr);try {// 有些保存接口是LIST,需要特殊处理if(bodyType.equals(List.class)){// 获取LIST的内部泛型类JSONArray jsonArray = JSONObject.parseArray(bodyStr);JSONArray decryptArray = new JSONArray();if (ObjectUtils.isEmpty(type)){this.body = IOUtils.toInputStream(jsonArray.toJSONString(), "UTF-8");return;}ParameterizedType parameterizedType = (ParameterizedType) type;Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();Class<?> elementType = (Class<?>) actualTypeArguments[0];for (Object o : jsonArray) {decryptArray.add(decode(JSONObject.parseObject(JSONObject.toJSONString(o)),elementType));}// 传递到接口this.body = IOUtils.toInputStream(decryptArray.toJSONString(), "UTF-8");}else {// 先转成JSON对象JSONObject jsonObject = JSONObject.parseObject(bodyStr);// 解密JSONObject finObject = decode(jsonObject, bodyType);// 传递到接口this.body = IOUtils.toInputStream(finObject.toJSONString(), "UTF-8");}} catch (Exception e) {log.error("加密参数【{}】解密失败:{}", bodyStr, e.getMessage(), e);// 传递到接口this.body = IOUtils.toInputStream(bodyStr, "UTF-8");}}@Overridepublic InputStream getBody() {return body;}@Overridepublic HttpHeaders getHeaders() {return headers;}}/*** 递归解密*/private JSONObject decode(JSONObject jsonObject,Class type) throws IllegalAccessException, NoSuchMethodException, NoSuchFieldException {// 取对象里的属性Set<String> keys = jsonObject.keySet();// 取接受入参里的所有属性Field[] fields = FieldsUtils.getClassAllFields(type);// 匹配两者,解密、字段类型转换for (Field field : fields) {field.setAccessible(true);// 字段名String fieldName = field.getName();// 字段类型Class fieldType = field.getType();// 如果JSON中没有,则直接跳过if(!keys.contains(fieldName)){continue;}// 是否是集合boolean isCollectionType = Collection.class.isAssignableFrom(fieldType);// 当前字段是否带有解密注解if(field.isAnnotationPresent(Decrypt.class)){// 需要解密,一定是要String类型String val = jsonObject.getString(fieldName);// 解String finVal = DesUtil.decode(val);// 判断原来是什么类型//log.info("fileType:{}",fieldType);if(ClassUtils.isPrimitiveOrWrapper(fieldType)){// 基础数据类型 直接替换jsonObject.put(fieldName,finVal);}else if(fieldType.equals(List.class)){// LIST集合,用array的接口换成LIST去接收jsonObject.put(fieldName,JSONObject.parseArray(finVal));}else{if(isJsonString(finVal)) {jsonObject.put(fieldName,JSONObject.parseObject(finVal));}else{// 还存在一些BigDecimal类似的,无法被判断为基础数据类型,回到这里jsonObject.put(fieldName,finVal);}}}else if(fieldType.getPackage().getName().startsWith("com.lu.test")){// 如果是com.lu.test这个根包下的自定义对象,则递归向下找jsonObject.put(fieldName,decode(jsonObject.getJSONObject(fieldName),fieldType));}else if(isCollectionType){// 以JSONARRAY存储JSONArray jsonArray =  jsonObject.getJSONArray(fieldName);JSONArray decodeArray = new JSONArray();// 获取LIST中的泛型集合Type listType = field.getGenericType();ParameterizedType parameterizedType = (ParameterizedType) listType;Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();Class<?> elementType = (Class<?>) actualTypeArguments[0];// 遍历集合for (Object o : jsonArray) {// 获取元素属性decodeArray.add(decode(JSONObject.parseObject(JSON.toJSONString(o)),elementType));}jsonObject.put(field.getName(),decodeArray);}}return jsonObject;}/*** 判断是否是JSON字符串* @param jsonString* @return*/private static boolean isJsonString(String jsonString) {try {JSONObject.parseObject(jsonString);return true;} catch (Exception e) {return false;}}
}
  • 接口入参解密 (GET & POST FORM-DATA | URL)
/*** 入参解密** @author lu*/
@Slf4j
@Component
public class RequestParamDecryptAdvice implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException, IOException {//部分get请求不会带Content-Type/*if (ObjectUtils.isEmpty(request.getContentType())){return true;}*/// application/json 直接放过if(ObjectUtils.isNotEmpty(request.getContentType()) && request.getContentType().toLowerCase().contains("application/json")){return true;}if (ObjectUtils.isEmpty(request.getContentType())){log.warn("当前请求 contentType 为空!");}// 请求头里获取解密标识String flag = request.getHeader(DecryptRequestWrapper.DECRYPT_FLAG);if (StringUtils.isEmpty(flag)) {// 生成包装类DecryptRequestWrapper decryptRequest = new DecryptRequestWrapper(request);// 判断是否需要解密HandlerMethod handlerMethod = (HandlerMethod) handler;Method method = handlerMethod.getMethod();//获取参数名集合String[] parameterNames = ParameterUtil.getParameterNames(method);// 获取参数集合Parameter[] parameters = method.getParameters();if (ArrayUtils.isNotEmpty(parameters)) {try {// 遍历参数int i=0;for (Parameter parameter : parameters) {// 获取参数类型Class paramterType = parameter.getType();// 断参数是否是基本数据类型以及包装类 && 带有解密注解if (ClassUtils.isPrimitiveOrWrapper(paramterType) && parameter.isAnnotationPresent(Decrypt.class)) {// 获取密文//JDK版本必须是1.8及以上//编译时候必须有编译选项:javac -parameters打开,默认是关闭的// 否则这里parameter.getName()拿不到真实的参数名字if (i>parameters.length-1) {return true ;}String orginVal = request.getParameter(parameterNames[i]);// 空值不处理if (StringUtils.isEmpty(orginVal)) {log.warn("形参:{}未从入参中获取到值", parameterNames[i]);continue;}// 非空解密 覆盖decryptRequest.setParameter(parameterNames[i], DesUtil.decode(orginVal));}// 自动以RO处理else if (paramterType.getPackage().getName().startsWith("com.lu.test")) {// 获取所有字段Field[] fields = paramterType.getDeclaredFields();// 是否带解密注解(这里不递归,不考虑RO里还玩嵌套的,不是JSON格式的应该不存在这种情况)for (Field field : fields) {if (ClassUtils.isPrimitiveOrWrapper(field.getType()) && field.isAnnotationPresent(Decrypt.class)) {// 获取密文String orginVal = request.getParameter(field.getName());// 空值不处理if (StringUtils.isEmpty(orginVal)) {log.warn("形参:{}未从入参中获取到值", field.getName());continue;}// 非空解密 覆盖decryptRequest.setParameter(field.getName(), DesUtil.decode(orginVal));}}}i++;// 其他常见的 request/response对象这些都不处理}} catch (Exception e) {log.error("字段解密异常!", e);}}String uri = request.getRequestURI().replace(request.getContextPath(), "");request.getRequestDispatcher(uri).forward(decryptRequest, response);return false;}// 已经解密的return true;}@Overridepublic void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {}@Overridepublic void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {}
}
	HandlerInterceptor 需要结合WebMvcConfigurer才能生效
/*** @author lu*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {@AutowiredRequestParamDecryptAdvice requestParamDecryptAdvice;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(requestParamDecryptAdvice);}
}
  • 反射参数获取工具
/*** 自 java8 开始,可以通过反射得到方法的参数名,不过这有个条件:你必须手动在编译时开启-parameters 参数* 部署项目时不可能设置这种东西,*/
public class ParameterUtil {/*** Spring自带的参数提取工具类*/private static final DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();/*** 获取参数名** @param method 方法* @return 参数名*/@Nullablepublic static String[] getParameterNames(Method method) {return discoverer.getParameterNames(method);}/*** 获取参数名** @param ctor 构造函数* @return 参数名*/@Nullablepublic static String[] getParameterNames(Constructor<?> ctor) {return discoverer.getParameterNames(ctor);}
}
  • 应用场景

    // ResponseResult 为自定义的统一返回值@PostMapping("/query1")public ResponseResult<SupErpVo> querySup(@RequestBody SupErpRo ro) {return supService.querySup(ro);}
	// ResponseResult 为自定义的统一返回值// IPage 为分页插件返回值@GetMapping("/query2")public ResponseResult<IPage<SupErpVo>> querySup(@RequestParam @Decrypt Long headId) {return supService.querySup(headId);}
	@Datapublic class SupErpVo {// 可遍历字段加密@Encryptprivate Long id;private String supName;private String supDep;}
	@Datapublic class SupErpRo {// 加密字段解密@Decryptprivate Long id;private String supName;}

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

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

相关文章

氢气传感器报警值:守护实验室安全的隐形卫士

随着科技的发展&#xff0c;我们的生活变得越来越便捷&#xff0c;但是与此同时&#xff0c;安全问题也日益凸显。其中&#xff0c;氢气作为一种清洁能源&#xff0c;被广泛应用于各个领域&#xff0c;但是如果不加以控制&#xff0c;氢气泄漏也可能带来严重的安全隐患。因此&a…

柔性电流探头方向判断有哪些方法?干货分享!

柔性电流探头方向判断的方法干货分享&#xff01;从理论到实践&#xff0c;助您成为专业人士&#xff01;干货收藏&#xff0c;快看起来吧&#xff01;      柔性电流探头方向判断一直是电力行业测试中的关键问题之一&#xff0c;确切地判断电流方向对于测试电力系统的稳定…

Docker部署xxl-job调度器并结合SpringBoot测试

文章目录 一、Docker部署1. 创建数据库2. 启动容器3. 访问4. 新建执行器 二、SpringBoot整合1. 模块注册到执行器2. 创建配置类3. 启动测试 三、任务发布-普通任务1. 编写任务代码2. 创建任务3. 启动任务 四、任务发布-分片任务1. 编写任务代码2. 启动多个实例3. 创建任务4. 启…

MYSQL Case When搜索查询

Case When可以用作select语句中的搜索&#xff0c;如果满足条件&#xff0c;就给字段附上结果值。 语法&#xff1a; CASE WHEN 表达式1 THEN 结果1 WHEN 表达式2 THEN 结果2WHEN 表达式3 THEN 结果3ELSE 其他结果 END简单的例子&#xff1a; 当学生表tb_student里&#xff…

CTF特训(二):青少年CTF-MISC部分WP

FLAG&#xff1a;当觉得自己很菜的时候&#xff0c;就静下心来学习 专研方向:MISC&#xff0c;CTF 每日emo&#xff1a;听一千遍反方向的钟&#xff0c;我们能回到过去吗&#xff1f; CTF特训(二)&#xff1a;青少年CTF-MISC部分WP&#xff1a; 文章目录 CTF特训(二)&#xff1…

IDEA开发使用 thymeleaf 模板$表达式报红波浪线解决方案

系列文章目录 文章目录 系列文章目录后端存值前端取值thymeleaf 后端存值 RequestMapping("/testModelAndView")//使用ModelAndView时返回的方法类型必须是ModelAndViewpublic ModelAndView testModelAndView() {//创建ModelAndView对象ModelAndView mav new Model…

QT播放gstreamer命令(三)---使用QMediaPlayer

前文&#xff1a; 因为之前听说过&#xff0c;QMediaPlayer已经集成了gstreamer&#xff0c;但是并没有什么接口来例子来说明&#xff0c;根本看不出来有任何gstreamer的形式&#xff0c;于是在QT5助手里面搜了一下&#xff0c;发现确实有gstreamer的痕迹&#xff0c;但是例子写…

vue中template原理

在Vue中&#xff0c;template具有非常重要的作用&#xff0c;它能将数据渲染到用户界面上&#xff0c;达到我们所预想的效果。Vue的template是基于类似于html的语法&#xff0c;但是它还有一些独特的规则。在Vue中&#xff0c;template语法会被转换成虚拟DOM&#xff0c;在和Vu…

谷歌浏览器网站打不开,显示叹号

问题&#xff1a; 您与此网站之间建立的连接不安全请勿在此网站上输入任何敏感信息&#xff08;例如密码或信用卡信息&#xff09;&#xff0c;因为攻击者可能会盗取这些信息。 了解详情 解决方式&#xff1a; 网上有很多原因&#xff0c;亲测为DNS问题&#xff0c;设置&…

Qt之窗口位置

Qt提供了很多关于获取窗体位置及显示区域大小的函数&#xff0c;如x&#xff08;&#xff09;&#xff0c;y()和pos()&#xff0c;rect()&#xff0c;size()&#xff0c;geometry()等&#xff0c;统称为"位置相关函数"或"位置函数"。几种主要位置函数及其之…

82.1W/S的QPS到底大不大?

QPS&#xff0c;全称是Query Per Second&#xff0c;即每秒查询次数。它是一种衡量系统处理能力的重要指标。"每秒1万"的QPS对于一般的个人网站或者中小型网站来说&#xff0c;是相当高的。但是对于大型网站、互联网公司或高并发系统来说&#xff0c;可能就略显不足。…

第0章 Linux 基础入门

第0章 Linux 基础入门 RHCSA Red Hat Certified System Administrator 红帽认证系统管理员。 什么是计算机 计算机的组成&#xff1a; 控制器 运算器 存储器 输出设备 输入设备 计算机只能识别0和1&#xff0c;也就是二进制数。 为什么要学习Linux Linux 因其高效率…

Python||五城P.M.2.5数据分析与可视化_使用华夫图分析各个城市的情况(上)

目录 五城P.M.2.5数据分析与可视化——北京市、上海市、广州市、沈阳市、成都市&#xff0c;使用华夫图分析各个城市的情况 1.北京市的空气质量 2.广州市的空气质量 【上海市和成都市空气质量情况详见下期】 五城P.M.2.5数据分析与可视化——北京市、上海市、广州市、沈阳市、成…

重发布

一&#xff1a;作用 在两种路由协议之间&#xff0c;或者一个协议的不同进程之间&#xff0c;借助ASBR &#xff08;同时工作在两种协议或 者协 议的不同进程中&#xff09;学习到两个网络的路由信息&#xff0c;并且通过重发布进行路由共享&#xff0c;最终实现全网可 达。…

在mysql中我有一列字段,字段类型是Decimal,保留了2位小数,我想过滤这2位小数中不为0的数字,如何过滤呢?

直接看SQL如何实现 SELECT * FROM your_table_name WHERE amount - FLOOR(amount) > 0;解释 这里的FLOOR(amount)函数会返回amount的整数部分&#xff0c;amount - FLOOR(amount)就会得到小数部分。如果小数部分大于0&#xff0c;表示这个数字的小数部分不为0 扩展 如果…

大模型实践笔记(1)——GLM-6B实践

目录 在Ubuntu上的配置Git Large File Storage 安装Git LFS&#xff1a; 设置Git LFS&#xff1a; 使用Git LFS&#xff1a; 安装GLM-6B 环境依赖 ChatGLM2-6B介绍 配置GLM 下载代码 构建环境 安装依赖 本地部署 网页UI 很多模型在hugging face上面&#xff0c;…

知识库系统为什么优秀企业都必备?这篇文章告诉你

在今天的商业世界中&#xff0c;知识就是力量。企业里的每一个小小的灵感、想法、经验&#xff0c;都可能是推动业务增长的源泉。那么&#xff0c;如何系统地管理和使用这些宝贵的知识资源呢&#xff1f;答案就是——知识库系统。 那么&#xff0c;什么是知识库系统呢&#xf…

六、Nacos源码系列:Nacos健康检查

目录 一、简介 二、健康检查流程 2.1、健康检查 2.2、客户端释放连接事件 2.3、客户端断开连接事件 2.4、小结 2.5、总结图 三、服务剔除 一、简介 Nacos作为注册中心不止提供了服务注册和服务发现的功能&#xff0c;还提供了服务可用性检测的功能&#xff0c;在Nacos…

货车运输(树)

[NOIP2013 提高组] 货车运输 题目背景 NOIP2013 提高组 D1T3 题目描述 A 国有 n n n 座城市&#xff0c;编号从 1 1 1 到 n n n&#xff0c;城市之间有 m m m 条双向道路。每一条道路对车辆都有重量限制&#xff0c;简称限重。 现在有 q q q 辆货车在运输货物&#x…

【面试深度解析】快手后端一面:G1、IOC、AOP、并发、JVM生产问题定位、可重复读、ThreadLocal

欢迎关注公众号&#xff08;通过文章导读关注&#xff1a;【11来了】&#xff09;&#xff0c;及时收到 AI 前沿项目工具及新技术的推送&#xff01; 在我后台回复 「资料」 可领取编程高频电子书&#xff01; 在我后台回复「面试」可领取硬核面试笔记&#xff01; 文章导读地址…