依桐博客是一个网络资源分享平台，建站初衷是为了提供一个舒心、有爱的交流环境，希望各位都能感受到「家」的温暖！
介绍当我们有个固定的模板，但是里面某些值不一样，可以使用该工具类将里面的变量进行替换！使用案例对象传递 （支持取子类或父类）User user = new User(); user.setName("依桐"); user.setAge(21); User.Detail detail = new User.Detail(); user.setDetail(detail); User.Detail.Ability ability = new User.Detail.Ability(); ability.setHobbyList(new ArrayList<String>() {{ add("Java"); }}); detail.setAbility(ability); String contentByObj = "我是${user.name} ,今年${user.age}岁, 我的爱好：${user.detail.ability.hobbyList}"; String result = PlaceholderHelper.resolve(contentByObj, user); //输出内容：我是依桐 ,今年21岁, 我的爱好：[Java] System.out.println(result); Map传递（支持取多维Map/对象）User user = new User(); user.setName("依桐 - 对象"); user.setAge(21); User.Detail detail = new User.Detail(); detail.setPhone("123456789"); user.setDetail(detail); User.Detail.Ability ability = new User.Detail.Ability(); ability.setHobbyList(new ArrayList<String>() {{ add("Java"); }}); detail.setAbility(ability); Map<String, Object> valueMap = new HashMap<>(); valueMap.put("name", "依桐"); valueMap.put("age", 21); valueMap.put("user", user); String contentByMap = "我是${name} ,今年${age}岁, 我的爱好：${user.detail.ability.hobbyList}"; String result = PlaceholderHelper.resolve(contentByMap, valueMap); //输出内容：我是依桐 ,今年21岁, 我的爱好：[Java] System.out.println(result); PlaceholderHelper 占位符解析器工具代码/** * 占位符解析器 * * @author www.jufb.cn */ public class PlaceholderHelper { /** * 默认前缀占位符 */ public static final String DEFAULT_PLACEHOLDER_PREFIX = "${"; /** * 默认后缀占位符 */ public static final String DEFAULT_PLACEHOLDER_SUFFIX = "}"; /** * 占位符前缀长度 */ public static final int PREFIX_LENGTH = DEFAULT_PLACEHOLDER_PREFIX.length(); /** * 占位符后缀长度 */ public static final int SUFFIX_LENGTH = DEFAULT_PLACEHOLDER_SUFFIX.length(); /** * 根据map解析占位符 * * @param content 内容 * @param valueMap map * @return 解析后的内容 */ public static String resolve(String content, Map<String, Object> valueMap) { return resolveByRule(content, placeholder -> { String[] fieldNames = placeholder.split("\\."); //如果是多级占位符 如：user.name 那么可能是map中的key，也可能是对象中的属性 优先取map中的key 如果没有则取对象中的属性 if (fieldNames.length > 1) { //先取map中的key Object value = valueMap.get(placeholder); if (null != value) { return String.valueOf(value); } //获取根对象 如：user.name 那么先取user对象 Object obj = valueMap.get(fieldNames[0]); return String.valueOf(getFieldValue(fieldNames, obj)); } return String.valueOf(valueMap.get(placeholder)); }); } /** * 根据对象解析占位符 * * @param content 内容 * @param obj 对象 * @return 解析后的内容 */ public static String resolve(String content, Object obj) { if (null == content || null == obj) { return content; } return resolveByRule(content, placeholder -> { String[] fieldNames = placeholder.split("\\."); //获取根对象 如：user.name 那么先取user对象 return String.valueOf(getFieldValue(fieldNames, obj)); }); } /** * 获取对象中字段的值 * * @param fieldNames 字段名数组 * @param obj 对象 * @return 属性值 */ private static Object getFieldValue(String[] fieldNames, Object obj) { //适配直接根据对象中的属性获取值 如：name 实际上是取user.name 但是必须从根对象开始取 if (fieldNames.length == 1) { return getFieldValue(obj, fieldNames[0]); } //取对象中的属性 如：user.name 那么先取user对象，然后取name属性 for (int i = 1; i < fieldNames.length; i++) { //获取属性值 Object fieldValue = getFieldValue(obj, fieldNames[i]); //如果属性值为空，则返回null字符串 if (null == fieldValue) { return "null"; } //如果属性值不为空，则将属性值赋值给objClass obj = fieldValue; } return obj; } /** * 获取对象中字段的值 * * @param obj 对象 * @param fieldName 字段名 * @return 属性值 */ public static Object getFieldValue(Object obj, String fieldName) { if (null == obj || null == fieldName) { return null; } //多级Map嵌套的情况 如：userMap.detailMap.name 那么先取userMap，然后取detailMap，最后取name属性 if (obj instanceof Map) { return ((Map<?, ?>) obj).get(fieldName); } //获取字段 Field field = getField(obj.getClass(), fieldName); //获取字段的值 return getFieldValue(field, obj); } /** * 用于获取对象中字段的值 * * @param field 字段 * @param obj 对象 * @return 属性值 */ public static Object getFieldValue(Field field, Object obj) { if (null == field) { return null; } //将字段设置为可访问 就是将private修饰符的字段也能访问 makeAccessible(field); try { return field.get(obj); } catch (IllegalAccessException e) { throw new RuntimeException(e); } } /** * 从类中获取字段 * * @param clazz 类 * @param fieldName 字段名 * @return */ public static Field getField(Class<?> clazz, String fieldName) { if (null == clazz || null == fieldName || fieldName.trim().isEmpty()) { return null; } // 获取所有属性 while (clazz != Object.class && clazz != null) { for (Field declaredField : clazz.getDeclaredFields()) { if (fieldName.equals(declaredField.getName())) { return declaredField; } } // 获取父类 递归查找 clazz = clazz.getSuperclass(); } return null; } /** * 根据规则解析占位符 * * @param content 内容 * @param rule 规则 回调函数 * @return 解析后的内容 */ public static String resolveByRule(String content, Function<String, String> rule) { // 获取第一个占位符的位置开始 int start = content.indexOf(DEFAULT_PLACEHOLDER_PREFIX); if (start == -1) { return content; } // 用于替换占位符的内容 StringBuilder result = new StringBuilder(content); while (start != -1) { //从初始占位符开始查找结束符 int end = result.indexOf(DEFAULT_PLACEHOLDER_SUFFIX, start); //获取占位符属性值，如${id}, 即获取id String placeholder = result.substring(start + PREFIX_LENGTH, end); //替换整个占位符内容，即将${id}值替换为替换规则回调中的内容 String replaceContent = placeholder.trim().isEmpty() ? "" : rule.apply(placeholder); result.replace(start, end + SUFFIX_LENGTH, replaceContent); //从替换后的内容中向后继续查找占位符 start = result.indexOf(DEFAULT_PLACEHOLDER_PREFIX, start + replaceContent.length()); } return result.toString(); } /** * 设置字段为可见 * * @param field 字段 */ public static void makeAccessible(Field field) { if ((!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers()) || Modifier.isFinal(field.getModifiers())) && !field.isAccessible()) { field.setAccessible(true); } } }
源码介绍可以批量检测域名是否备案，如果有符合条件的域名，那么则会返回域名的ICP备案信息接口调用截图源码解析该接口的实现方法：通过curl模拟访问站长之家的查询页面，通过正则获取关键信息使用介绍单个PHP文件，直接上传到服务器就可以使用了，无需数据库！接口地址(例)：localhost/icp.php?domainList=jufb.cn,baidu.com参数详情：支持单个或批量查询，如有多个域名以,隔开，单次最大支持20个源码下载地址：https://ycm.lanzouy.com/iYFiY0pss77g
业务场景任务计划会每隔一段时间去检测设备的状态保存到A表，我们需要保存设备最后一次的检测状态到B表SQL数据表示例首先我们有两张表，一张是device_last_status（用于存储设备最后一次检测状态），另一张是device_plan（用于存储每次计划的检测记录）SQL语句insert into device_last_status (device_id, online, real_video) select device_id, online, real_video from device_plan as p where "planId" = '1' on conflict (device_id) do update set (device_id, online, real_video) = ( EXCLUDED.device_id, EXCLUDED.online, EXCLUDED.real_video ) 语法解释conflict里的字段必须为主键或者唯一索引，可以多个字段作为唯一索引，在数据库设置唯一，不然会报 "there is no unique or exclusion constraint matching the ON CONFLICT specification"错误插入和更新并不是批量数据一一对应操作的 而是批量insert然后再判断是否更新 所以下面只能写死一条数据 多了就报错pgsql在这种情况下会提供一个EXCLUDED临时表把之前插入的值储存起来
Maven导包 com.github.liuyueyi.media qrcode-plugin 3.0.0 使用样例普通二维码BufferedImage image = QrCodeGenV3.of("https://www.jufb.cn/").build().asImg();中间带LOGO的二维码BufferedImage image = QrCodeGenWrapper.of("https://www.jufb.cn/") //黑色方块变小方块 .setDrawStyle(QrCodeOptions.DrawStyle.MINI_RECT) .setDrawEnableScale(true) // 设置二维码颜色 .setDrawPreColor(new Color(203, 156, 73)) // 设置LOGO .setLogo("/image/icon.png") // 设置logo大小 .setLogoRate(8) // 设置LOGO四角样式 .setLogoStyle(QrCodeOptions.LogoStyle.NORMAL) //设置精度 .setErrorCorrection(ErrorCorrectionLevel.M) //设置二维码大小 .setW(400) //true 则探测图形有本身的绘制规则 .setDetectSpecial() .asBufferedImage();常用方法获取图片Base64public String createJumpQRCode(String url) throws Exception { BufferedImage image = QrCodeGenV3.of("https://www.jufb.cn/").build().asImg(); String QRCodeBase64Image = null; ByteArrayOutputStream os = new ByteArrayOutputStream(); try { ImageIO.write(image, “png”, os); QRCodeBase64Image = Base64.encode(os.toByteArray()); } finally { os.flush(); os.close(); } return "data:image/png;base64," + QRCodeBase64Image; } 更多详细样例https://hhui.top/quick/quick-media/qrcode/3.x二维码基本使用姿势/
介绍需要https的支持，可将Base64或URL图片复制到黏贴版中！ /** * 复制图片，接受 data: URI 和 http[s] 的图片地址 * @param url * @returns {Promise<unknown>} */ function image2blob (url) { return new Promise(resolve => { // 如果是 data 开头的就使用 DataURI 的解析 if (url.indexOf('data:') === 0) { return resolve(new ClipboardItem({ 'image/png': dataURItoBlob(url) })) } const img = new Image const c = document.createElement('canvas') const ctx = c.getContext('2d') img.onload = function () { c.width = this.naturalWidth c.height = this.naturalHeight ctx.drawImage(this, 0, 0) c.toBlob(blob => resolve(new ClipboardItem({ 'image/png': blob })), 'image/png', 1) } img.crossOrigin = '' img.src = url }) } /** * 将 data: 开头的 dataURI base64 图片转为 blob * @param dataURI * @returns {Blob} */ function dataURItoBlob (dataURI) { var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0] // mime类型 var byteString = atob(dataURI.split(',')[1]) //base64 解码 var arrayBuffer = new ArrayBuffer(byteString.length) //创建缓冲数组 var intArray = new Uint8Array(arrayBuffer) //创建视图 for (var i = 0; i < byteString.length; i++) { intArray[i] = byteString.charCodeAt(i) } return new Blob([intArray], { type: mimeString }) } /** * 复制图片 * @param url * @returns {Promise<unknown>} */ function copyImage (url) { return new Promise((resolve, reject) => { image2blob(url).then(data => { navigator.clipboard.write([data]).then(resolve, reject) }) }) } window.onload = function () { if (!document.hasFocus()) { alert('必须窗口获得焦点') return } copyImage(...).then(() => alert('复制成功'), (e) => alert(e)) }
简介XXL-JOB是一个分布式任务调度平台，其核心设计目标是开发迅速、学习简单、轻量级、易扩展。现已开放源代码并接入多家公司线上产品线，开箱即用。gitee码云地址: https://gitee.com/xuxueli0323/xxl-job部署 XXL-JOB 后台在gitee码云中下载源码后导入到IDEA中,首先将SQL文件导入到数据库中, 然后修改properties数据库连接配置, 最后直接启动即可, 启动后在浏览器地址输入: http://127.0.0.1:8080/xxl-job-admin/ 即可进入到后台管理页面, 账号:admin 密码:123456SpringBoot整合XXL-JOB1.Maven导包 com.xuxueli xxl-job-core 2.3.1 2.创建XxlJobConfig类XxlJobConfig这个类在码云下载的源码中有,直接copy过来就可以!3.在yml中配置相关参数server: port: 9001 xxl: job: accessToken: default_token admin: addresses: http://127.0.0.1:8080/xxl-job-admin executor: appname: xxl-job-executor-sample #执行期管理名称 需要与后台配置的一致 port: 9999 ip: logpath: ./data/applogs/xxl-job/jobhandler logretentiondays: 304.建立任务文件/** * demoJobHandler 每个任务都有JobHandler名称 在后台任务管理中配置 */ @XxlJob("demoJobHandler") public void demoJobHandler() { //获取任务参数 String jobParam = XxlJobHelper.getJobParam(); System.out.println("demoJobHandler 任务执行了..."); }5.进行启动测试6.执行效果
首先准备一些数据字段为: 设备名称,目录名称,是否在线List集合根据设备目录分组为Map集合Map<String, List<Device>> groupByCatalogName = deviceList.stream().collect(Collectors.groupingBy(Device::getCatalogName)); 统计List集合中所有设备在线/离线数量//在线数量 long onlineCount = deviceList.stream().filter(Device::getOnline).count(); //离线数量 long offlineCount = deviceList.stream().filter(device -> !device.getOnline()).count(); 根据设备目录进行分组统计数量Map<String, Long> countGroupByCatalog = deviceList.stream().collect(Collectors.groupingBy(Device::getCatalogName, Collectors.counting())); List转Map Key=>Value形式Map<String, String> deviceMap = deviceList.stream().collect(Collectors.toMap(Device::getDeviceName, Device::getCatalogName)); List转Map 名称字段作为Key,实体类ValueMap<String, Device> deviceMap = deviceList.stream().collect(Collectors.toMap(Device::getDeviceName, account -> account));
获取当前年月日LocalDate now = LocalDate.now(); //2022-12-18 int year = LocalDate.now().getYear(); //2022 int month = LocalDate.now().getMonth().getValue(); //12 int day = LocalDate.now().getDayOfMonth(); //18 获取当前时分秒LocalTime now = LocalTime.now(); //19:21:55.125 int hour = LocalTime.now().getHour(); //19 int minute = LocalTime.now().getMinute(); //21 int second = LocalTime.now().getSecond(); //55 获取当前年-月-日 时:分:秒DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); LocalDateTime now = LocalDateTime.now(); String nowTime = dtf.format(now); System.out.println(dateFormat); //2022-12-18 19:21:55 日期时间之间格式相互转换String nowTime = "2022-12-18 19:21:55"; Date date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(nowTime); String dateFormat = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒").format(date); System.out.println(dateFormat); //2022年12月18日 19时21分55秒
SpringBoot使用自定义注解实现AOP记录用户日志功能
问题在项目中,想记录用户的日志,如果每个方法都写一遍记录日志入库操作,那么代码肯定不优雅,我们可以通过AOP去记录日志入库操作,只需写一遍代码,通过自定义注解形式去识别哪些需要记录的日志!Maven导包 org.springframework spring-context org.aspectj aspectjweaver AOP记录日志注解代码/** * AOP记录日志 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface LogAop { String code() default ""; } 使用方式在方法上声明注解 @LogAop(code = "USER_PWD_LOGIN")AOP记录日志功能实现 /** * 切入点表达式 */ @Pointcut("execution(* cn.jufb.user.service.*.*(..))") private void pt() { } /** * 记录日志 * * @param pjp * @return * @throws Throwable */ @Around("pt()") public Object recordLog(ProceedingJoinPoint pjp) throws Throwable { //方法返回结果 Object resultData = pjp.proceed(); //获取LogAop注解中的 日志类型编码 MethodSignature methodSignature = (MethodSignature) pjp.getSignature(); Method method = methodSignature.getMethod(); LogAop logAop = method.getAnnotation(LogAop.class); if (null == logAop || StringUtils.isBlank(logAop.code())) { return resultData; } String logType = logAop.code(); //... } 效果处理方案此时我们已经拿到了,我们自定义的注解值,以上图为例: 此时拿到了USER_PWD_LOGIN这个值,我们可以进行入库或其他操作,如果想转译成中文或者是还想在库中记录详细信息,我们可以通过枚举,通过我们传过来的值进行获取其他信息等
问题在开发微服务项目中,想在gatway网关header头添加密钥字段向下传递,密钥配置在了yml文件中,结果在下面服务中使用@Value获取不到yml的值,一顿百度结果发现拦截器是手动new出来的, 此时并没有交给Spring去管理, 只需重新new一个拦截器并在方法中声明@Bean交给Spring去管理,并修改addInterceptors方法中注册拦截器的方式即可效果此时通过@Value注解即可获取到响应的值