首页
关于
Search
1
sql的注入原因和解决办法
146 阅读
2
SpringBoot整合腾讯云存储COS及基本使用,增删改查......
134 阅读
3
深究contains方法
101 阅读
4
多线程概述
76 阅读
5
学习的第一个注解@WebServlet - JavaWeb
74 阅读
默认分类
Java
C/C++
Mysql
JavaWeb
SpringBoot
算法
前端
Linux
Search
标签搜索
Spring
HTTP
Java
JavaWeb
IOC
mybatis
腾讯云
COS
云存储
CDN
redis
分布式
id
全局唯一id
Typecho
累计撰写
26
篇文章
累计收到
2
条评论
首页
栏目
默认分类
Java
C/C++
Mysql
JavaWeb
SpringBoot
算法
前端
Linux
页面
关于
搜索到
26
篇与
achong
的结果
2023-07-28
基于Redis实现的分布式全局唯一ID
分布式id的要求唯一性:可以在多个系统之间保持唯一性。高性能:redis是基于内存的数据库,性能极高。高可用:redis支持集群。递增性:具有单调递增的特性安全性:id拼接了其他信息,递增的规律性不会太明显。基于redis的id的组成id由三个部分组成,符号位,时间戳,序列号。使用long类型存储,长整型占八个字节,1个字节等于8个比特位,即64位。第1位表示符号位,永远是0;第2~32位表示时间戳,以秒为单位,可以使用69年;剩下的33~64位表示序列号,秒内的计数器,支持每秒产生2^32个不同的id;代码实现1、生成时间戳需要事先准备一个固定的起始时间戳,先运行main方法里面的代码获得。每次生成id时,获取当前时间戳。通过“起始时间戳”减去“当前时间戳”,获得组成id的时间戳。// 1、 private static final long BEGIN_TIMESTAMP = 1672531200L; public static void main(String[] args){ // 获得一个时间戳,时间为2023年1月1日0点0分0秒 LocalDateTime time = LocalDateTime.of(2023, 1, 1, 0, 0, 0); long timestamp = time.toEpochSecond(ZoneOffset.UTC); System.out.println(timestamp); // 1672531200 } // 2、 LocalDateTime now = LocalDateTime.now(); long nowSecond = now.toEpochSecond(ZoneOffset.UTC); long timestamp = nowSecond = BEGIN_TIMESTAMP; 2、生成序列号其实就是在redis里设置一个key,通过对key自增,每次获得id使用自增后的值,组成序列号部分。需要特别注意key的组成,因为redis中key的自增上限是2^64次方,虽然已经很大了,但毕竟还是有上限。所以解决办法就是“key+日期”。这里将key拼接上生成id时的年月日,这样做的好处是,key会随着年月日变化;还有利于统计,某天的key自增的值就代表当前生成的id的数量。/** * “icr”:自增key的标识,可改 * “keyPrefix”:业务id * “date”:当前年月日 * * 例如:icr:order:2023:07:28 */ String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd")); long count = stringRedisTemplate.opsForValue().increment("icr:"+keyPrefix+":"+date);3、组成idlong类型是占64个bit,此时将其左移32位,原来的32个位置上会自动补0。然后再将结果和序列号进行“或运算”,就得到了完整id。long id = timestamp << 32 | count; return id;4、图解:原始时间戳 左移32位的时间戳 或运算之后的时间戳此时id的前32位是符号位和时间戳组成,后32位是存储在redis里的那个自增key的值。id会随时间戳而变化,而时间戳是精确到秒,所以理论上,就算在极高的并发下,只要一秒内并发不超过2^32次方,id就是唯一的。完整代码@Component public class RedisIdWorker { // 开始时间戳 private static final long BEGIN_TIMESTAMP = 1672531200L; private StringRedisTemplate stringRedisTemplate; public RedisIdWorker(StringRedisTemplate stringRedisTemplate) { this.stringRedisTemplate = stringRedisTemplate; } public long nextID(String keyPrefix){ // 1、生成时间戳 LocalDateTime now = LocalDateTime.now(); long nowSecond = now.toEpochSecond(ZoneOffset.UTC); long timestamp = nowSecond = BEGIN_TIMESTAMP; // 2、生成序列号 // 获取当前日期,精确到天。用以解决redis中,单key自增上限问题,还有利于统计。redis单key自增上限为2^64次方 String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd")); // 自增长 long count = stringRedisTemplate.opsForValue().increment("icr:"+keyPrefix+":"+date); // 3、拼接并返回 //生成的时间戳左移32位,然后或上自增长的值 long id = timestamp << 32 | count; return id; } }性能测试用线程池模拟并发,for循环提交任务。 private ExecutorService es = Executors.newFixedThreadPool(500); @Test void testIdWorker() throws InterruptedException { CountDownLatch cdl = new CountDownLatch(500); Runnable task = () -> { for (int i = 0; i < 100; i++) { long id = redisIdWorker.nextID("order"); System.out.println(id); } cdl.countDown(); }; StopWatch stopWatch = new StopWatch(); stopWatch.start(); for (int i = 0; i < 500; i++) { es.submit(task); } cdl.await(); stopWatch.stop(); // time = 3961 System.out.println("time = " + stopWatch.getTotalTimeMillis()); }本地电脑测试结果:5万个id,用时3秒多。
2023年07月28日
8 阅读
0 评论
0 点赞
2023-04-11
SpringBoot整合腾讯云存储COS及基本使用,增删改查......
本次测试环境基于JDK1.8、SpringBoot,依赖由Maven管理。本文章对腾讯云官方的api文档进行了测试,且略有改进,并适当添加注释。1、环境配置腾讯云存储对应的JDK文档页面https://cloud.tencent.com/document/product/436/10199先引入API相关依赖<dependency> <groupId>com.qcloud</groupId> <artifactId>cos_api</artifactId> <version>5.6.97</version> </dependency>在resource资源目录下配置相关文件,存储腾讯云密钥和云存储的基本信息config.properties### 腾讯云 # 密钥 qcloud.secretId=AKID********xtSLn5XWDI86bm qcloud.secretKey=d1I********1vd6 # cos配置 # region,桶存在的区域 qcloud.region=ap-shanghai # bucketName,即桶的名字 qcloud.bucketName=test-1200000000 # 访问域名 qcloud.Domain=https://test-1200000000.cos.ap-shanghai.myqcloud.com/在config类里配置相关Bean@Configuration public class Configure { /** * 该Bean用于读取资源目录下的config.properties文件 */ @Bean("cosConfig") public Properties cosConfig(){ Properties properties = new Properties(); try(InputStream is = ClassLoader.getSystemResourceAsStream("config.properties")) { properties.load(is); } catch (IOException e) { throw new RuntimeException(e); } return properties; } /** * 注册一个配置好的COS客户端 */ @Bean("cosClient") public COSClient createCOSClient(){ // 读取配置文件获取密钥 Properties properties = cosConfig(); String secretId = properties.getProperty("qcloud.secretId"); String secretKey = properties.getProperty("qcloud.secretKey"); String sessionToken = "TOKEN"; // 传入密钥 BasicSessionCredentials credentials = new BasicSessionCredentials(secretId, secretKey, sessionToken); COSCredentials cosCredentials = new BasicCOSCredentials(secretId, secretKey); // 设置bucket的地域 Region region = new Region(properties.getProperty("qcloud.region")); ClientConfig clientConfig = new ClientConfig(region); // 设置https clientConfig.setHttpProtocol(HttpProtocol.https); // 可选:设置socket读取超时,默认30s clientConfig.setSocketTimeout(5*1000); // 可选:设置建立连接超时时间,默认30s clientConfig.setConnectionTimeout(5*1000); // 如果需要的话,设置 http 代理,ip 以及 port //clientConfig.setHttpProxyIp("httpProxyIp"); //clientConfig.setHttpProxyPort(80); // 生成cos客户端 COSClient cosClient = new COSClient(cosCredentials, clientConfig); return cosClient; } }云存储的增删改查皆由COSClient 类来发起请求和接收接口。根据腾讯云的文档可知:COSClient 是线程安全的类,允许多线程访问同一实例。 因为实例内部维持了一个连接池,创建多个实例可能导致程序资源耗尽。请确保程序生命周期内实例只有一个,且在不再需要使用时,调用 COSClient.shutdown() 方法将其关闭。如果需要新建实例,请先将之前的实例关闭。推荐一个进程里只使用一个 COSClient,在程序全部结束退出时才调用 COSClient.shutdown()。所以将COSClient 交给spring管理刚刚好,因为spring管理的Bean默认是单实例的。2、上传文件文件上传:对象存储中本身没有文件夹和目录的概念,文件的完整路径用Key和Value表示,例如有一个文件的完整路径是/2023/04/11/1.png,此时,“/2023/04/11/”是Key,“1.png”是Value。 @Resource public Properties cosConfig; @Resource private COSClient cosClient; /** * 使用简单接口上传,文件类型 */ @Test public void cosClientUploadFile(){ String bucketName = cosConfig.getProperty("qcloud.bucketName"); File file = new File("1.webp"); String KV = "/test/" + file.getName(); /** * 参数1:桶名称 * 参数2:上传后存放的完整路径。完整路径+文件名 * 参数3:要上传的文件 */ // PutObjectRequest 用于设置请求信息 PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, KV, file); try{ // 如果这里没有发生异常,则表示本次请求操作成功。 // PutObjectResult 类用于返回结果信息 PutObjectResult putObjectResult = cosClient.putObject(putObjectRequest); System.out.println(putObjectResult.getRequestId()); } catch (CosServiceException e) { e.printStackTrace(); } catch (CosClientException e) { e.printStackTrace(); } // 确认本进程不再使用 cosClient 实例之后,关闭之 cosClient.shutdown(); }流上传:/** * 使用简单接口上传,流类型 * 上传的源是一个 InputStream 类型(和其子类型)的流实例。 */ @Test public void cosClientUploadStream(){ String bucketName = cosConfig.getProperty("qcloud.bucketName"); File file = new File("1.webp"); String KV = "test/" + file.getName(); try(FileInputStream fis = new FileInputStream(file)) { // ObjectMetadata 类用于记录对象的元信息 ObjectMetadata objectMetadata = new ObjectMetadata(); PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, KV, fis, objectMetadata); PutObjectResult putObjectResult = cosClient.putObject(putObjectRequest); System.out.println(putObjectResult.getRequestId()); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } // 确认本进程不再使用 cosClient 实例之后,关闭之 cosClient.shutdown(); }3、下载文件从指定桶下载文件@Test public void cosLoadTest(){ String bucketName = cosConfig.getProperty("qcloud.bucketName"); /** * 创建一个get请求 * 参数1:桶名称 * 参数2:文件的key */ GetObjectRequest getObjectRequest = new GetObjectRequest(bucketName, "test/2.png"); File downloadFile = new File("11.jpg"); try { /** * 返回一个异步结果 Download, 可同步的调用 waitForCompletion 等待下载结束, 成功返回 void, 失败抛出异常 * 下载的文件回存入downloadFile中 */ Download download = transferManager.download(getObjectRequest, downloadFile); download.waitForCompletion(); } catch (CosServiceException e) { e.printStackTrace(); } catch (CosClientException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } transferManager.shutdownNow(true); }4、删除文件从指定桶删除文件@Test public void deleteCOSFile(){ String bucketName = cosConfig.getProperty("qcloud.bucketName"); String key = "test/1.jpg"; cosClient.deleteObject(bucketName, key); }5、查询文件只有批量顺序查询,没有单个查询单次批量查询官方的说法是列出第一页对象,第一页的数量可以设置,但是最大1000.很简陋,且查出来的数据是无序的。 @Test public void queryList1(){ String bucketName = cosConfig.getProperty("qcloud.bucketName"); // new一个专门的请求对象 ListObjectsRequest listObjectsRequest = new ListObjectsRequest(); // 设置bucketName名称 listObjectsRequest.setBucketName(bucketName); // 设置列出的对象名以prefix为前缀,可找出文件名前缀相同的对象 listObjectsRequest.setPrefix(""); // 设置最大列出多少个对象,一次listObject最大支持1000 listObjectsRequest.setMaxKeys(10); /** * 发起请求,并保存列出的结果 * 成功:返回 ObjectListing 类型, 包含所有的成员, 以及 nextMarker,是上一批列表中的最后一个对象的名字 * 失败:抛出异常 CosClientException 或者 CosServiceException。详情请参见 异常处理。 */ ObjectListing objectListing = null; try { objectListing = cosClient.listObjects(listObjectsRequest); }catch (CosServiceException e){ e.printStackTrace(); }catch (CosClientException e){ e.printStackTrace(); } // object summary表示此次列出的对象列表 List<COSObjectSummary> cosObjectSummaries = objectListing.getObjectSummaries(); System.out.println("本次列出数据条数为:" + cosObjectSummaries.size()); for (COSObjectSummary cosObjectSummary : cosObjectSummaries) { // 对象的 key String key = cosObjectSummary.getKey(); // 对象的 etag String etag = cosObjectSummary.getETag(); // 对象的长度 long fileSize = cosObjectSummary.getSize(); // 对象的存储类型 String storageClasses = cosObjectSummary.getStorageClass(); System.out.println(cosConfig.getProperty("qcloud.Domain") + key + ", " + etag + ", " + fileSize + ", " + storageClasses); } cosClient.shutdown(); }多次批量查询同样简陋,查出来的数据也是无序的。但是可以循环查询全部数据。代码解读: 请求流程都在do-while里完成,即先查一遍,然后再判断是否未查完。如未查完则携带标记继续循环查询。 由客户端cosClient发起的查询请求会返回一个列表对象objectListing,该对象有一个方法objectListing.isTruncated()可以判断本次查询的列表的后面是否还有数据,如果有,该方法会返回true,然后用objectListing.getNextMarker()方法可以获取下一次标记,其实就是本次结果列表的最后一条数据的key,然后将这个key标记设置到请求对象里面,下次查询复用这个对象即可。 下次开始发送请求时,listObjectsRequest对象将会携带上一次标记,然后该次请求就会从该标记位置开始返回数据,如此循环往复,就可以查完全部数据。 @Test public void queryList2(){ String bucketName = cosConfig.getProperty("qcloud.bucketName"); boolean flag = false; String netMarker = ""; do { // 每次循环开始时,先设置循环标记为false,等本次请求发现没有列完时,再设置回true,让while继续循环。 flag = false; ListObjectsRequest listObjectsRequest = new ListObjectsRequest(); // 设置bucketName名称 listObjectsRequest.setBucketName(bucketName); // 设置列出的对象名以prefix为前缀 listObjectsRequest.setPrefix(""); // 设置最大列出多少个对象,一次listObject最大支持1000 listObjectsRequest.setMaxKeys(10); // 设置标记位置 listObjectsRequest.setMarker(netMarker); ObjectListing objectListing = null; try { objectListing = cosClient.listObjects(listObjectsRequest); }catch (CosServiceException e){ e.printStackTrace(); }catch (CosClientException e){ e.printStackTrace(); } // 打印出结果列表里的对象 List<COSObjectSummary> cosObjectSummaries = objectListing.getObjectSummaries(); System.out.println("本次列出数据条数为:" + cosObjectSummaries.size()); for (COSObjectSummary cosObjectSummary : cosObjectSummaries) { // 对象的 key String key = cosObjectSummary.getKey(); // 对象的 etag String etag = cosObjectSummary.getETag(); // 对象的长度 long fileSize = cosObjectSummary.getSize(); // 对象的存储类型 String storageClasses = cosObjectSummary.getStorageClass(); System.out.println(cosConfig.getProperty("qcloud.Domain") + key + ", " + etag + ", " + fileSize + ", " + storageClasses); } // 判断是否查完 if (objectListing.isTruncated()){ // 表示还没有列完,被截断了 // 这里的返回值是一个字符串,具体是本次结果集的最后一条数据的key netMarker = objectListing.getNextMarker(); System.out.println(netMarker); flag = true; } }while (flag); cosClient.shutdown(); }6、列出指定目录下的目录及文件 只能查到下面一级,如果想查深一点,可以获取到目录的时候再递归查询目录 @Test public void queryList3(){ String bucketName = cosConfig.getProperty("qcloud.bucketName"); ListObjectsRequest listObjectsRequest = new ListObjectsRequest(); // 设置bucketName listObjectsRequest.setBucketName(bucketName); // 这里填要列出的目录的相对 bucket 的路径 listObjectsRequest.setPrefix("/test/"); // delimiter 表示目录的截断符, 例如:设置为 / 则表示对象名遇到 / 就当做一级目录) listObjectsRequest.setDelimiter("/"); // 设置最大遍历出多少个对象, 一次 listobject 最大支持1000 listObjectsRequest.setMaxKeys(100); // 保存每次列出的结果 ObjectListing objectListing = null; do { try{ // 发起请求 objectListing = cosClient.listObjects(listObjectsRequest); } catch (CosServiceException e) { e.printStackTrace(); return; } catch (CosClientException e) { e.printStackTrace(); return; } // 这里保存列出来的子目录 List<String> commonPrefixes = objectListing.getCommonPrefixes(); System.out.println("目录"); for (String commonPrefix : commonPrefixes) { System.out.println(commonPrefix); } // 这里保存列出的对象列表 List<COSObjectSummary> cosObjectSummaries = objectListing.getObjectSummaries(); System.out.println("对象"); for (COSObjectSummary cosObjectSummary : cosObjectSummaries) { // 对象的 key String key = cosObjectSummary.getKey(); System.out.println(key); } // 标记下一次开始的位置 String nextMarker = objectListing.getNextMarker(); listObjectsRequest.setMarker(nextMarker); }while (objectListing.isTruncated()); }7、判断文件是否存在 @Test public void isExistFile(){ String bucketName = cosConfig.getProperty("qcloud.bucketName"); String key = "/test/2.png"; try { boolean result = cosClient.doesObjectExist(bucketName, key); if (result){ System.out.println("存在"); }else { System.out.println("不存在"); } }catch (CosServiceException e){ e.printStackTrace(); }catch (CosClientException e){ e.printStackTrace(); } }8、查询对象的元数据 @Test public void queryObjectMetaData(){ String bucketName = cosConfig.getProperty("qcloud.bucketName"); String key = "/test/2.png"; try { ObjectMetadata objectMetadata = cosClient.getObjectMetadata(bucketName, key); System.out.println(objectMetadata.getCrc64Ecma()); System.out.println(objectMetadata.getLastModified()); System.out.println(objectMetadata.getETag()); System.out.println(objectMetadata.getRequestId()); }catch (CosServiceException e){ e.printStackTrace(); }catch (CosClientException e){ e.printStackTrace(); } cosClient.shutdown(); }9、修改对象元数据 /** * 修改对象元数据 * 修改对象元数据利用了复制对象的接口,在复制过程中设置新的元数据。 * 使用复制对象接口,在复制过程中设置新的元数据。在复制接口中仅仅修改元数据,不会执行对象数据的复制。 */ @Test public void changeObjectMetaData(){ String bucketName = cosConfig.getProperty("qcloud.bucketName"); String key = "/test/2.png"; // 获取当前的对象元数据 ObjectMetadata objectMetadata = cosClient.getObjectMetadata(bucketName, key); // 修改对象元数据必须设置 replaced objectMetadata.setHeader("x-cos-metadata-directive", "Replaced"); // 设置新的对象元数据 // 注意:Content-Disposition 、自定义元数据或者其他有中文的头域值,在设置前请先调用 UrlEncoderUtils.encode(String) 编码,避免签名问题 objectMetadata.setHeader("x-cos-storage-class", "STANDARD_IA"); objectMetadata.setContentType("text/plain"); /** * copyObjectRequest:拷贝文件请求 * * 重点: * 参数1: 源 Bucket region。默认值:与当前 clientConfig 的 region 一致,表示同地域拷贝 * 参数23:表示源文件得桶名称和key。 * 参数45:表示目的桶和key。 * 如果参数23和参数45相同,则是修改操作。相当于linux命令中,用mv给文件改名。 */ Region region = new Region(cosConfig.getProperty("qcloud.region")); CopyObjectRequest copyObjectRequest = new CopyObjectRequest(region, bucketName, key, bucketName, key); copyObjectRequest.setNewObjectMetadata(objectMetadata); try { CopyObjectResult copyObjectResult = cosClient.copyObject(copyObjectRequest); System.out.println(copyObjectResult.getRequestId()); } catch (CosServiceException e) { e.printStackTrace(); } catch (CosClientException e) { e.printStackTrace(); } cosClient.shutdown(); }10、获取对象的访问远程URL根据key,获得一个完整的可远程访问的该文件的URL。https://abc-1200000000.cos.ap-shanghai.myqcloud.com/test/2.png其实域名是固定且可知的,在控制台的桶信息里看到,本身就知道完整Key,可以直接本地拼接URL,不需要再一次查询。 @Test public void getURL(){ String bucketName = cosConfig.getProperty("qcloud.bucketName"); String key = "/test/2.png"; URL url = cosClient.getObjectUrl(bucketName, key); System.out.println(url); }
2023年04月11日
134 阅读
1 评论
0 点赞
2023-03-03
Spring组件扫描原理 - 基础版
以下代码只是基本的组件扫描功能。全是Java的基础知识。类加载器、反射、注解、文件和文件夹操作、String的处理、集合。public static void main(String[] args) { Map<String, Object> beanMap = new HashMap<>(); // 1、需要扫描的目录 String packageName = "cn.qqwer.achong.IOC.bean"; // 2、将包名转换为路径格式 String packagePath = packageName.replaceAll("\\.","/"); // cn/qqwer/achong/IOC/bean // 3、通过系统加载器获取本地资源路径,返回一个URL对象 URL url = ClassLoader.getSystemClassLoader().getResource(packagePath); // 4、得到绝对路径 String path = url.getPath(); // 5、根据绝对路径得到file对象 File file = new File(path); // 6、获取路径下的所有类文件,获得数组 File[] files = file.listFiles(); // 7、开始逐个处理 Arrays.stream(files).forEach(f -> { // 8、拼接类文件的全包名 String className = packageName + "." +f.getName().split("\\.")[0]; // cn.qqwer.achong.IOC.bean.User try { // 9、根据类的全路径,获取类本身 Class<?> aClass = Class.forName(className); // 10、判断此类是否标记Component注解 if (aClass.isAnnotationPresent(Component.class)){ Component annotation = aClass.getAnnotation(Component.class); // 11、如果有注解、再继续判断@Component是否有值,即是否为bean设置ID String id = annotation.value(); if ("".equals(annotation.value())){ String simpleName = aClass.getSimpleName(); // 12、如果没为bean设置id,则将类文件名设置为bean的ID,类名首字母需转小写 if (!Character.isLowerCase(simpleName.charAt(0))){ id = new StringBuilder().append(Character.toLowerCase(simpleName.charAt(0))).append(simpleName.substring(1)).toString(); } } // 13、最好,将对象new出,并放到一个Map中。 Object obj = aClass.newInstance(); beanMap.put(id, obj); } } catch (Exception e) { throw new RuntimeException(e); } }); //输出map查看结果 System.out.println(beanMap); }
2023年03月03日
16 阅读
0 评论
0 点赞
2023-02-19
对文本框监听事件的优化,时间换性能。
正常情况下,js对文本框控件正常的监听是这样子的。// 对id为 #content 的控件添加文本修改事件 $(document).on("input propertychange","#content",function(res){ var newValue = $("#content").val(); console.log(newValue) }); 可以正常监听文本的变化。但是,太过灵敏了,某些情况下可能不适合。 比如这样一个需求:网页有一个实时同步功能的文本框,文本框内的数据需要实时发送到服务器存储,以便下一次展现。 在打字的情况下,每按下一个字母,都会触发一次文本修改。如果修改监听的回调函数里面是向服务器发送请求的话,即使用户在正常的字的情况下,对服务器来说像是遭受CC攻击了一样,频率太快了。 所以,我对代码进行了改进,时间精度换性能。var n = 0; $(document).on("input propertychange", "#content", function(res){ // 设置文本框监听 var nn = ++n; setTimeout(function(){ if (n != nn) return; UpData(); }, 1000); }); 通过对比文本修改次数来判断需不需要向服务器发送数据。上面代码中定义了两个变量,一个变量n在外面记录 回调函数 文本变化的次数;另一个临时变量nn记录在 回调函数 执行的那一刻文本变化次数。在 回调函数 里用setTimeout来定时2秒执行一个函数,1秒后,如果内外两个记录文本修改次数的变量相同,则向服务器发送数据,否则直接结束本次执行。 如此循环往复,当用户真正停下来,或者用户暂停撰写时,才往服务器发送本次修改的文本。虽然文本不是真正的实时发送,但是慢一两秒对这个需求来说不是很重要。所以说是时间换性能。
2023年02月19日
11 阅读
0 评论
0 点赞
2023-02-14
动态SQL:解决insert时的不确定字段
记录一个真实情况。有这样一个需求:前端传进一个实体对象,后端需要将其存储。此时应该这样写:controller:public ReturnInfo regUri(Url url){ ...... }mapper: <insert id="insertUrl" parameterType="url"> INSERT INTO url_info(uri, data, create_time, expiration_time) VALUE (#{uri}, #{data}, #{createTime}, #{expirationTime}); </insert>但是,其中有一个属性可能是null,比如,在现在的实体中,expirationTime这个过期时间参数可以为空。这时候可以使用mybatis的动态sql功能的 if标签 对其进行判断,根据expirationTime这个属性的有无来修改sql语句。 <insert id="insertUrl" parameterType="url"> INSERT INTO url_info(uri,data, create_time <if test="expirationTime != null and expirationTime != ''"> , expiration_time </if> ) VALUE (#{uri},#{data}, #{createTime} <if test="expirationTime != null and expirationTime != ''"> , #{expirationTime} </if> ); </insert>使用<if></if>标签可以根据实体属性,判断标签里的内容是否拼接进sql语句中。
2023年02月14日
30 阅读
0 评论
0 点赞
2022-10-27
SpringBoot集成腾讯云短信JDK,实现短信发送!!!
腾讯云原文地址,原文更详细,我下面的代码略有修改,只保留了与发送短信相关的代码。https://cloud.tencent.com/document/product/382/43194#.E5.8F.91.E9.80.81.E7.9F.AD.E4.BF.A1腾讯云短信控制台: https://console.cloud.tencent.com/smsv2 短信签名、模板ID、短信应用ID项目pom文件引入腾讯云短信接口的sdk <dependency> <groupId>com.tencentcloudapi</groupId> <artifactId>tencentcloud-sdk-java</artifactId> <!-- go to https://search.maven.org/search?q=tencentcloud-sdk-java and get the latest version. --> <!-- 请到https://search.maven.org/search?q=tencentcloud-sdk-java查询所有版本,最新版本如下 --> <version>3.1.612</version> </dependency>配置tencentCloud.properties文件在resources目录下,用于保存腾讯云的用户API密钥tencent.cloud.secretId=AKc6Si**********BtQmvtiMo5me5S tencent.cloud.secretKey=NYWIuK**********spdIE1fPW tencent.cloud.sdkAppId=短信应用ID tencent.cloud.signName=短信签名内容 tencent.cloud.templateId=模板 ID创建一个实体类,用于读取密钥与短信相关配置@Component @Data @PropertySource(value = "classpath:tencentCloud.properties", encoding = "UTF-8") @ConfigurationProperties(prefix = "tencent.cloud") public class TencentCloudProperties { // 腾讯云账户密钥对 private String secretId; private String secretKey; // 短信应用ID private String sdkAppId ; // 短信签名内容 private String signName ; // 模板 ID private String templateId ; }将发送短信相关代码封装成一个工具类 经过封装后,调用该方法时只需传入手机号码和验证码,事后会返回SDK的一个短信发送结果封装类SendSmsResponse。package com.achong.utils; import com.achong.bean.TencentCloudProperties; import com.tencentcloudapi.common.Credential; import com.tencentcloudapi.common.exception.TencentCloudSDKException; import com.tencentcloudapi.sms.v20210111.SmsClient; // 注意 SendSmsRequest 和 SendSmsResponse 是在同一个包下 import com.tencentcloudapi.sms.v20210111.models.SendSmsRequest; import com.tencentcloudapi.sms.v20210111.models.SendSmsResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class SMSUtils { @Autowired private TencentCloudProperties tencentCloudProperties; /** * 发送短信 post请求 */ public SendSmsResponse sendSMS(String phone, String verCode){ System.out.println("=========================================="); System.out.println(tencentCloudProperties.toString()); System.out.println("=========================================="); // 实例化一个认证对象,入参需要传入腾讯云账户密钥对secretId,secretKey。 Credential cred = new Credential(tencentCloudProperties.getSecretId(), tencentCloudProperties.getSecretKey()); /* 实例化要请求产品的client对象 * 第一个参数是认证对象 * 第二个参数是地域信息,可以直接填写字符串ap-guangzhou,支持的地域列表参考 https://cloud.tencent.com/document/api/382/52071#.E5.9C.B0.E5.9F.9F.E5.88.97.E8.A1.A8 */ SmsClient client = new SmsClient(cred, "ap-guangzhou"); // 实例化一个请求对象,根据调用的接口和实际情况,可以进一步设置请求参数 SendSmsRequest request = new SendSmsRequest(); /** * 以下是填充请求信息 */ /* 短信应用ID: 短信SdkAppId在 [短信控制台] 添加应用后生成的实际SdkAppId,示例如1400006666 */ // 应用 ID 可前往 [短信控制台](https://console.cloud.tencent.com/smsv2/app-manage) 查看 request.setSmsSdkAppId(tencentCloudProperties.getSdkAppId()); /* 短信签名内容: 使用 UTF-8 编码,必须填写已审核通过的签名 */ // 签名信息可前往 [国内短信](https://console.cloud.tencent.com/smsv2/csms-sign) 或 [国际/港澳台短信](https://console.cloud.tencent.com/smsv2/isms-sign) 的签名管理查看 request.setSignName(tencentCloudProperties.getSignName()); /* 模板 ID: 必须填写已审核通过的模板 ID */ // 模板 ID 可前往 [国内短信](https://console.cloud.tencent.com/smsv2/csms-template) 或 [国际/港澳台短信](https://console.cloud.tencent.com/smsv2/isms-template) 的正文模板管理查看 request.setTemplateId(tencentCloudProperties.getTemplateId()); /* 模板参数: 模板参数的个数需要与 TemplateId 对应模板的变量个数保持一致,若无模板参数,则设置为空 */ String[] templateParamSet = {verCode}; request.setTemplateParamSet(templateParamSet); /* 下发手机号码,采用 E.164 标准,+[国家或地区码][手机号] * 示例如:+8613711112222, 其中前面有一个+号 ,86为国家码,13711112222为手机号,最多不要超过200个手机号 */ String[] phoneNumberSet = {"86" + phone}; request.setPhoneNumberSet(phoneNumberSet); /* 通过 client 对象调用 SendSms 方法发起请求。注意请求方法名与请求对象是对应的 * 返回的 response 是一个 SendSmsResponse 类的实例,与请求对象对应 */ SendSmsResponse response = null; try { response = client.SendSms(request); } catch (TencentCloudSDKException e) { e.printStackTrace(); } // 输出json格式的字符串回包 System.out.println(SendSmsResponse.toJsonString(response)); //方法将返回 短信发送结果 的对象 return response; } } 创建Controller调用上面封装的工具类发送短信进行测试。package com.achong.controller; import com.achong.utils.SMSUtils; import com.tencentcloudapi.sms.v20210111.models.SendSmsResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class SendSMSController { @Autowired private SMSUtils smsUtils; @RequestMapping("/sendSms") public void sendSms(String phone){ if (phone == null || phone == "" || phone.length() != 11) { System.out.println("手机号错误"); return; } // 本地生成四位数验证码,短信位数可调整 10000 的位数 String code = (int)((Math.random() * 9 + 1) * 10000) + ""; // 调用工具类发送短信,参数分别是 手机号码 和 验证码。 SendSmsResponse response = smsUtils.sendSMS(phone, code); // 格式化打印输出短信发送结果 System.out.println(SendSmsResponse.toJsonString(response)); // 拿到 短信发送结果 里的 Code 字段,该字段表示 短信是否发送成功 String status = response.getSendStatusSet()[0].getCode(); if ("Ok".equals(status)){ System.out.println("发送成功,状态码:"+status); }else { System.out.println("发送失败,状态码:"+status); } } } 浏览器测试访问地址:http://localhost:8080/sendSms?phone=17310101010发送成功后,页面会返回短信发送结果,格式为 JSON字符串,其中Code字段为短信发送结果,成功则该字段为Ok,否则是其他错误信息。该字段可以作为判断短信发送是否成功的依据。 { "SendStatusSet": [{ "SerialNo": "3369:76164835516668596094641947", 发送流水号。 "PhoneNumber": "+8617310101010", 手机号码,E.164标准,+[国家或地区码][手机号] ,示例如:+8613711112222, 其中前面有一个+号 ,86为国家码,13711112222为手机号。 "Fee": 1, 计费条数 "SessionContext": "", 用户 session 内容。 "Code": "Ok", 短信请求错误码 "Message": "send success", 短信请求错误码描述。 "IsoCode": "CN" 国家码或地区码,例如 CN、US 等,对于未识别出国家码或者地区码,默认返回 DEF }], "RequestId": "b9a8468f-12bb-4985-bda7-5592d56f98bd" }
2022年10月27日
66 阅读
0 评论
2 点赞
2022-08-12
bs_typeadhead - 关键字自动补全插件
bs_typeadhead - 文本框关键字自动补全插件此插件基于bootstrap首先引入三大部分的依赖jquery、bootstrap、bs_typeadhead<%-- jquery--%> <script type="text/javascript" src="jquery/jquery-1.11.1-min.js"></script> <!-- bootstrap --> <link rel="stylesheet" href="jquery/bootstrap_3.3.0/css/bootstrap.min.css"> <script type="text/javascript" src="jquery/bootstrap_3.3.0/js/bootstrap.min.js"></script> <%-- bs_typeadhead--%> <script type="text/javascript" src="jquery/bs_typeahead/bootstrap3-typeahead.min.js"></script>页面添加一个div容器<input type="text" id="customerName">在入口函数,对容器注册bs_typeadhead 函数部分说明:typeahead( ) 函数的source属性就是自动补全的数据的来源,来源分为静态数据和动态数据。静态数据:可以是数组 [ ]。动态数据:source是一个函数,里面是ajax请求,请求的结果必须是可遍历的json对象<script type="text/javascript"> $(function (){ $("#customerName").typeahead({ //数据的来源-静态 // source:['京东商城','阿里巴巴','百度科技公司','字节跳动','动力节点'] //数据的来源-动态 source:function(jquery,process){ //参数列表里的process是一个函数,作用是把ajax返回的data赋值给属性source。 //用户在容器中获取的关键字 //发送请求 $.ajax({ url:'workbench/transaction/queryCustomerNameByName.do?customerName'+jquery, type:'post', dataType:'json', success:function (data){ process(data); //参数列表里的process是一个函数,作用是把ajax返回的data赋值给属性source。 } }); } }); });ajax请求部分对应的后端代码 @RequestMapping("/workbench/transaction/queryCustomerNameByName.do") @ResponseBody public List<String> queryCustomerNameByName(String customerName){ List<String> customerNameList = customerService.queryCustomerNameByName(); return customerNameList; }typeahead( ) 函数的其他属性source: 规定包含查询时要显示的值的数据源。值的类型是 array,默认值是 [ ]。items: 规定查询时要显示的条目的最大值。数据类型是 number,默认值是 8。matcher: 决定查询是否匹配条目。带有一个单一的参数,即要测试查询的条目。当前查询通过 this.query 访问。返回一个布尔值 true,表示查询匹配。数据类型是 function。默认情况下是大小写不敏感的。sorter: 用于自动分类结果。带有一个单一的参数,即具有 typeahead 实例范围的条目。当前查询通过 this.query 访问。数据类型是 function。默认值是精确匹配的,其他的值还可以是大小写敏感、大小写不敏感。highlighter: 用于自动高亮突出显示结果。带有一个单一的参数,即具有 typeahead 实例范围的条目。数据类型是 function。默认情况下是高亮突出显示所有默认的匹配项。
2022年08月12日
51 阅读
0 评论
0 点赞
2022-08-01
日历插件datatimepicker的使用 - JSP
DateTimePicker图形化日历时间日期选择器DateTimePicker是基于JQuery的时间日期选择插件。只需要2行代码,即可轻松实现网页图形化日期时间选择器。 datatimepicker软件包下载地址:https://www.datetimepicker.cn/download/有一个编辑框<body> <input id="myDate" type="text" readonly> </body>然后按顺序引入相关css和jsjquery依赖bootstrap框架dataTimePicker插件<%-- jquery--%> <script type="text/javascript" src="jquery/jquery-1.11.1-min.js"></script> <%-- bootstrapk框架--%> <link rel="stylesheet" href="jquery/bootstrap_3.3.0/css/bootstrap.min.css"> <script type="text/javascript" href="jquery/bootstrap_3.3.0/js/bootstrap.min.js"></script> <%-- bootstrap datetimepicker插件--%> <link rel="stylesheet" href="jquery/bootstrap-datetimepicker-master/css/bootstrap-datetimepicker.min.css"> <script type="text/javascript" src="jquery/bootstrap-datetimepicker-master/js/bootstrap-datetimepicker.js"></script> <script type="text/javascript" src="jquery/bootstrap-datetimepicker-master/locale/bootstrap-datetimepicker.zh-CN.js"></script>在页面加载完成后注册插件<script type="text/javascript"> $(function (){ //当容器加载完成,对容器调用工具函数 $("#myDate").datetimepicker({ language:'zh-CN', //语言 format:'yyyy-mm-dd', //日期格式 minView:'month', //最小精度 autoclose:'true', //默认值false,当选择一个日期之后是否立即关闭此日期时间选择器。 initData:new Date(), //默认时间 todayBtn:'ture', //是否显示选择当前时间按钮 clearBtn:'true' //是否显示清空按钮 }); })完整代码<%@ page contentType="text/html;charset=UTF-8" language="java" %> <% String basePath=request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+request.getContextPath()+"/"; %> <html> <head> <base href="<%=basePath%>"> <meta charset="UTF-8"> <%-- jquery--%> <script type="text/javascript" src="jquery/jquery-1.11.1-min.js"></script> <%-- bootstrapk框架--%> <link rel="stylesheet" href="jquery/bootstrap_3.3.0/css/bootstrap.min.css"> <script type="text/javascript" href="jquery/bootstrap_3.3.0/js/bootstrap.min.js"></script> <%-- bootstrap datetimepicker插件--%> <link rel="stylesheet" href="jquery/bootstrap-datetimepicker-master/css/bootstrap-datetimepicker.min.css"> <script type="text/javascript" src="jquery/bootstrap-datetimepicker-master/js/bootstrap-datetimepicker.js"></script> <script type="text/javascript" src="jquery/bootstrap-datetimepicker-master/locale/bootstrap-datetimepicker.zh-CN.js"></script> <title>演示bs_datetimepicker插件</title> <script type="text/javascript"> $(function (){ //当容器加载完成,对容器调用工具函数 $("#myDate").datetimepicker({ language:'zh-CN', //语言 format:'yyyy-mm-dd', //日期格式 minView:'month', //最小精度 autoclose:'true', //默认值false,当选择一个日期之后是否立即关闭此日期时间选择器。 initData:new Date(), //默认时间 todayBtn:'ture', //是否显示选择当前时间按钮 clearBtn:'true' //是否显示清空按钮 }); }) </script> </head> <body> <input type="text" id="myDate" readonly> </body> </html>
2022年08月01日
66 阅读
0 评论
0 点赞
2022-07-09
手动注入JdbcTemplate - SpringBoot
在Spring的某些无法自动注入的情况下,可以选择手动注入。数据源:就是配置数据库的驱动、连接地址、账号、密码//配置数据源 DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306/userdb?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai"); dataSource.setUsername("root"); dataSource.setPassword("123456"); //new出JdbcTemplate,并将数据源放入其构造方法 JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);例子将注入过程放到类的构造方法比较合理。@SpringBootTest public class mysqlTest { private JdbcTemplate jdbcTemplate; public mysqlTest(){ DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306/userdb?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai"); dataSource.setUsername("root"); dataSource.setPassword("123456"); this.jdbcTemplate = new JdbcTemplate(dataSource); } @Test public void contextLoads(){ Long count = jdbcTemplate.queryForObject("select count(*) from userinfo", Long.class); System.out.println("userinfo数据表记录总数为:" + count); } }
2022年07月09日
35 阅读
0 评论
0 点赞
2022-05-16
记一个二分查找
被查找的数组必须是有序。 public class HelloWorld{ public static void main(String[] args){ //被查找数组,必须是有序 int[] arr = {1,3,5,7,9,11,13,14}; //被查找目标 int target = 14; //定义左、右、中间下标 int start = 0; int end = arr.length - 1; int mid; //循环结束条件:左下标 小于等于 右下标。 while(start <= end){ mid = (start + end) / 2; //初始化中间下标 //如果中间元素和目标相同,直接return。否则继续判断 if(target == arr[mid]) { System.out.println("ok, target = " + mid); return; } else if(target > arr[mid]) { //如果目标大于中间值,说明目标在中间值的右边。 //把左下标改到中间下标的后一位。 start = mid+1; } else { //如果不符合上面,目标就一定小于中间值。 //把右下标改为中间下标的前一位 end = mid-1; } } //若程序执行到这里,说明没找到。 System.out.println("NO"); } }
2022年05月16日
53 阅读
0 评论
2 点赞
2022-05-15
Spring - IOC
[TOC]一、IOC控制反转IOC就是对象创建的由谁来控制。正转:由程序员进行对象的创建和依赖注入称之为正传。就是程序员说了算。Student.stu = new Student( ); ===>程序员创建对象 stu.setName("张三"); ===>程序员进行赋值 stu.setAge(18); 反转:由Spring容器创建对象和依赖注入称为反转,将控制权从程序员手上夺走,由Spring管理,称之为反转。<bean id="stu" class="com.achong.entity.Student"> ===>Spring容器负责对象的创建 <property name="name" value="张三"> ===>Spring容器依赖注入值,也就是对对象赋值 <property name="age" value="20"> </bean>二、Bean管理Bean管理是指两个操作:Spring创建对象 和 Spring给对象的属性注入值。Bean管理有两种实现方式:基于XML配置文件 和 基于注解 。三、IOC接口IOC思想基于IOC容器完成,IOC容器底层就是对象工厂。Spring提供IOC容器实现的两种方式:BeanFactory和ApplicationContextBeanFactoryIOC容器的基本实现,是Spring内部使用的接口,一般不提供开发人员使用。ApplicationContextBeanFactory接口的子接口,提供更多更强大的功能,一般由开发人员使用。两者的区别是:BeanFactory在加载配置文件的时候不会创建对象,在获取对象的时候采取创建对象;而ApplicationContext会在配置文件加载的时候就把对象创建好。四、 基于XML配置文件的Bean管理4.1.两种方式创建对象方式一:创建对象并注入值Bean标签是创建对象id是对象名称,名称唯一 ;class是对象的完全包名。propertory标签是为对象的属性注入值。若不需要注入则该标签不用写。name是属性名,value是属性值。注意:创建对象是调用无参构造方法,注入值是使用set方法。所以必须保证对象类里有这两种方法。<!--创建学生对象,在容器启动的时候--> <bean id="stu" class="com.achong.entity.Student"> <property name="name" value="张三"></property> <property name="age" value="18"></property> </bean> 取出对象 ClassPathXmlApplicationContext( )方法读取xml配置文件。 getBean( )方法取出创建好的对象。参数名为xml里对应Bean标签的id。 @Test public void TestSpringStudent(){ ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); Student stu = (Student) ac.getBean("stu"); System.out.println(stu ); }方式二:创建对象并注入值此方式调用的是带参的构造方法创建对象。使用 构造方法 的 参数名称 注入值。 <bean id="school" class="com.achong.entity.School"> <constructor-arg name="name" value="广东工程职业技术学院"></constructor-arg> <constructor-arg name="address" value="广东省清远市清城区"></constructor-arg> </bean>使用 构造方法 的 下标 注入值。 <bean id="stu" class="com.achong.entity3.Student"> <constructor-arg index="0" value="2005"></constructor-arg> <constructor-arg index="1" value="achong"></constructor-arg> <constructor-arg index="2" value="18"></constructor-arg> <constructor-arg index="3" ref="school"></constructor-arg> </bean>使用 构造方法 的 参数 的 顺序 注入值。<bean id="stu" class="com.achong.entity4.Student"> <constructor-arg value="2005"></constructor-arg> <constructor-arg value="achong"></constructor-arg> <constructor-arg value="18"></constructor-arg> <constructor-arg ref="school"></constructor-arg> </bean>4.2. 三种方式注入属性值简单类型注入 简单类型指的是八大基本数据类型。使用value属性注入。上面的例子就是简单类型注入。引用类型注入 引用类型指的是另一个对象。使用ref属性注入。集合类型注入 集合类型注入指数组,List,Map。 例如User类里包含另一个Address类。这时候就需要引用类型注入。代码实现 - 引用类型注入<!-- 创建学校对象--> <bean id="school" class="com.achong.entity2.School"> <property name="name" value="广东工程职业技术学院"></property> <property name="address" value="广东省清远市清城区中宿路"></property> </bean> <!-- 创建学生对象--> <bean id="stu" class="com.achong.entity2.Student"> <property name="name" value="achong"></property> <property name="age" value="20"></property> <property name="school" ref="school"></property> </bean>代码实现 - 集合类型注入<bean id="stu" class="com.achong.entity.Student"> <!--数组类型--> <property name="courses"> <array> <value>java</value> <value>Mysql</value> </array> </property> <!--List类型注入--> <property name="maps"> <list> <value>张三</value> <value>李四</value> </list> </property> <!--Map类型注入--> <property name="sets"> <set> <value>Mysql</value> <value>Redis</value> </set> </property> </bean>添加util约束空间,把list提取出来。提取出来的集合独立,可供多个Bean使用。 <util:list id="bookList"> <value>java</value> <value>Mysql</value> <value>Servlet</value> </util:list> <bean id="stu" class="com.achong.entity5.Student"> <property name="courseList" ref="bookList"></property> </bean>内部Bean写法 <bean id="stu" class="com.achong.entity2.Student"> <property name="name" value="achong"></property> <property name="age" value="20"></property> <property name="school"> <bean id="school" class="com.achong.entity2.School"> <property name="name" value="广东工程职业技术学院"></property> <property name="address" value="广东省清远市"></property> </bean> </property> </bean>4.3 属性值包含空值、特殊符号空值<property name="address"> <null/> </property>特殊符号 第二个中括号里面的内容为输入值。<property name="address"> <vlaue><[!CDATA[<<广东省>>]]></vlaue> </property>五、基于注解的Bean管理也称为DI(Dependency Injection),它是IOC的具体实现的技术。使用注解管理bean,可以省去在xml配置文件里写很多行代码,为xml减负。5.1 创建对象的注解@Component("指定的名称"):可以创建任意对象。@Controller("指定的名称"):专门用来创建控制器的对象(Servlet),这种对象可以接收用户的请求,可以返回处理结果给客户端。@Service("指定的名称"):专门用来创建业务逻辑层的对象,负责向下访问数据访问层,处理完成后的结果返回给界面层。Repository("指定的名称"):专门用来创建数据访问层的对象,负责数据库中的增删改查所有操作。四个注解的功能一模一样,可以混用,但是不建议,因为这本来就是用来区分三层结构的,value也可以不写,不写的话,创建的对象的名字按照驼峰命名法来命名。@Resource( ): 可以根据类型注入,根据名称注入。但是因为这个包是java拓展的而不是Spring提供的,所以不建议使用。5.2 属性值注入的注解值类型的注入@Value:用来给简单类型注入值。相当于构造函数赋值。引用类型的注入@Autowired:使用类型注入值,从整个Bean工厂中搜索同源类型的对象进行注入。@Autowired、@Quelifier:使用名称注入值,从整个Bean工厂中搜索相同名称的对象进行注入。5.3实现步骤添加包扫描只扫描指定包。指定包下面的,带有指定注解的类将被创建。<context:component-scan base-package="com.achong.entity"></context:component-scan>创建对象并注入@Component public class Student { @Value("2005") private int id; @Value("achong") private String name; @Value("18") private int age; @Autowired private School school; }添加测试类测试@Test public void Test01(){ ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); Student stu = (Student) ac.getBean("student"); System.out.println(stu); }注意:在有父子类的情况下,使用按类型注入,就意味着有多个可注入的对象,此时按照名称进行二次筛选,选中与被注入对象相同名称的对象进行注入。所以,如果有父子类,建议使用按名称注入。5.4 细节批量包扫描, 逗号隔开<context:component-scan base-package="com.achong.entity , com.achong.entity2 , com.achong.entity3"></context:component-scan>自定义包扫描规则<!-- use-default-filters 表示不使用默认扫描规则 include-filter 设置扫描哪些内容,这里表示 只扫描Controller这个注解 --> <context:component-scan base-package="com.achong" use-default-filters="false"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> <!-- exclude-filter 设置哪些内容不扫描,这里表示Controller这个注解不会被扫描 --> <context:component-scan base-package="com.achong"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan>5.4 配置类替代xml上面的方法需要使用到xml配置文件。但是,使用配置类可以替代配置文件。步骤: 创建一个空白类,添加相应注解@Configuration //标记为配置类,替代xml文件 @ComponentScan(basePackages = {"com.achong.entity4"}) //扫描路径 public class SpringConfig { }添加测试方法测试。注意:原本的 new AnnotationConfigApplicationContext(SpringConfig.class) 应改成new AnnotationConfigApplicationContext(SpringConfig.class); @Test public void Test04(){ ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class); Student student = (Student) ac.getBean("student"); System.out.println(student); }
2022年05月15日
49 阅读
0 评论
0 点赞
2022-04-30
sql的注入原因和解决办法
sql注入原因:sql注入的根本原因是用户输入的信息中含有sql语句的关键字,并且这些关键字参与了sql语句的编译过程,导致sql的原意被扭曲,进而达到sql注入。例如以下查询方法。 //该方法查询数据库是否有此账号密码,返回一个布尔值 private static boolean userQuery(String userName, String userPassword) { boolean flag = false; Connection conn = null; Statement stmt = null; ResultSet rs = null; try { conn = DBUtils.getConnection(); stmt = conn.createStatement(); //执行sql语句 String sql = "select * from userinfo where userName='"+userName+"' and userPassword='"+userPassword+"'"; rs = stmt.executeQuery(sql); if (rs.next()) flag = true; } catch (SQLException throwables) { throwables.printStackTrace(); }finally { DBUtils.close(conn,stmt,rs); } return flag; }用一个变量接收用户输入的密码,如果接收到的是一个正常的密码:123456,那么拼接后的sql语句就是正常的。select * from userinfo where userName='王五' and userPassword='123456'但是如果接收到的密码不是一个正常的密码,而是 abc' or '1'='1。这样的不正常密码带有sql的关键字or,再加上巧妙的组合,最终会使拼接后的sql语句就是这样的。分析以下sql的条件语句:因为or后面的条件 '1'='1' 是永远是正确的,所以前面的条件userName='123' and userPassword='abc' 可以无视。即使用户输入的账号密码不正确或者不存在,这条sql语句也会正常成功执行。且有返回值。select * from userinfo where userName='123' and userPassword='abc' or '1'='1'解决sql注入解决方法:只要用户输入的信息不参与sql语句的编译过程,问题就解决了。首先,将Statement接口改成子接口PreparedStatement。在编写sql语句的时候,在关键位置用 "?" 占位,这里吧?叫做占位符在JDBC第三步,获取数据库操作对象的时候,传入sql语句。这步是把sql预处理,也就是搭起sql语句的框架。下一步使用setString方法设置占位符对应的关键字。索引从1开始。注意:字符串用setString() , int类型数据用setInt()最后,执行sql就ok。不过还要注意,此时已经不需要传入sql语句,因为前面的预编译和设置占位就已经完成了。修改后的jdbc过程。//该方法查询数据库是否有此账号密码 private static boolean userQuery(String userName, String userPassword) { Connection conn = null; //将Statement接口改成PreparedStatement接口,这俩是继承关系。 PreparedStatement stmt = null; ResultSet rs = null; boolean flag = false; try { conn = DBUtils.getConnection(); //1.在sql语句的关键位置用?占位。 String sql = "select * from userinfo where userName=? and userPassword=?"; //2.预编译sql,也就是按处理好sql的框架 stmt = conn.prepareStatement(sql); //3.setString方法设置占位符对应的关键字,索引从1开始 stmt.setString(1,userName); stmt.setString(2,userPassword); //4.执行查询,但是这里不需要传sql rs = stmt.executeQuery(); if (rs.next()) flag = true; } catch (SQLException throwables) { throwables.printStackTrace(); } finally { DBUtils.close(conn,stmt,rs); } return flag; }虽然Preparedment的操作多一点,但是安全。因为它是先把带有 "?" 占位符的sql语句先编译了,然后通过setString方法在传入占位符对应的值,这样做即使传入的值带有mysql的关键字也没事,因为此时的sql语句已经预编译了,后面传入的值没有参与编译过程。如果用户还是传入不规范的数据,则sql执行会报错。
2022年04月30日
146 阅读
0 评论
1 点赞
2022-04-30
Servlet JDBC两种注册驱动的方法
第一种用java.sql.DriverManager下的registerDriver( ) 方法注册驱动。传入一个Driver()对象即可。// 1. 注册驱动 Driver driver = new Driver(); DriverManager.registerDriver(driver); //合写成一句 DriverManager.registerDriver(new Driver());第二种Class.forName("com.mysql.jdbc.Driver");第二种是较为常用的,因为比较简单。原理:先看一下Driver()方法的源码。在第五行有一个静态方法,静态方法里执行了注册驱动的语句。所以第二种方法的原理是:反射这个类com.mysql.jdbcDriver,使这个类的静态代码块执行,驱动就注册成功了public class Driver extends NonRegisteringDriver implements java.sql.Driver { public Driver() throws SQLException { } static { try { DriverManager.registerDriver(new Driver()); } catch (SQLException var1) { throw new RuntimeException("Can't register driver!"); } } }用第二种方式注册驱动的完整JDBC代码 - 查public class Query { public static void main(String[] args) { Connection conn = null; Statement stmt = null; ResultSet rs = null; try { //注册驱动 Class.forName("com.mysql.jdbc.Driver"); //获取数据库连接 String url = "jdbc:mysql://127.0.0.1:3306/userdb?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Hongkong&allowPublicKeyRetrieval=true"; String user = "root"; String password = "123456"; conn = DriverManager.getConnection(url, user, password); //获取数据库操作对象 stmt = conn.createStatement(); //执行sql语句 String sql = "select * from userinfo"; rs = stmt.executeQuery(sql); //处理结果集 while (rs.next()){ String value = rs.getString("userId")+", "+rs.getString("userName"); System.out.println(value); } } catch (SQLException throwables) { throwables.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); }finally { //关闭连接 if (rs != null) { try { rs.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } if (stmt != null) { try { stmt.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } if (conn != null) { try { conn.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } } } }
2022年04月30日
49 阅读
0 评论
0 点赞
2022-04-30
Servlet JDBC六步
网上有把步骤分为七步的,其实就是把准备数据库账号密码地址又分为了一个步骤六步注册驱动告诉数据库,即将来凝结哪个品牌的数据库获取连接表示jvm的进程和数据库进程之间的通道打开了。获取数据库操作对象、专门执行sql语句的对象执行sql语句DQL和DML处理查询结果集只有当第四步执行的时select语句的时候,才有第五步查询结果集。释放资源java和数据库之间属于进程间的通信,开启后一定要关闭。注意:只有查询操作才有第五步。因为查询外的操作只是返回"影响的行数",只需要用int类型变量接收就可以了。而查询操作返回的是一个ResultSet结果集,需要额外处理。查先提前获取所需对象Connection conn = null; Statement stmt = null; ResultSet rs = null;注册驱动// 1. 注册驱动 Driver driver = new Driver(); DriverManager.registerDriver(driver); //合写成一句 DriverManager.registerDriver(new Driver());获取数据库连接//先获取数据库url和账号密码 String url = "jdbc:mysql://127.0.0.1:3306/userdb?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Hongkong&allowPublicKeyRetrieval=true"; String user = "root"; String password = "123456"; //注册驱动 conn = DriverManager.getConnection(url,user,password);获取数据库操作对象Statement stmt = conn.createStatement();执行sql语句。该语句为查询操作,执行完后需接收结果String sql = "select * from userinfo"; ResultSet rs = stmt.executeQuery(sql);处理结果集while (rs.next()){ String value = rs.getString("userId")+", "+rs.getString("userName"); System.out.println(value); }释放数据库连接。查询操作有三个对象,需要分开关闭,并且处理异常。if (rs != null){ try { rs.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } if(stmt != null){ try { stmt.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } if(conn != null){ try { conn.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } }增删改第四步:执行sql增删改语句的返回值是”影响的行数“,int类型变量接收即可。if接收到的变量是否为一就可以知道是否执行成功。int count = statement.executeUpdate(sql); System.out.println(count==1?"成功":"失败");六步完整代码public class Query { public static void main(String[] args) { Connection conn = null; Statement stmt = null; ResultSet rs = null; try { //注册驱动 DriverManager.registerDriver(new Driver()); //获取数据库连接 String url = "jdbc:mysql://127.0.0.1:3306/userdb?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Hongkong&allowPublicKeyRetrieval=true"; String user = "root"; String password = "123456"; conn = DriverManager.getConnection(url, user, password); //获取数据库操作对象 stmt = conn.createStatement(); //执行sql语句 String sql = "select * from userinfo"; rs = stmt.executeQuery(sql); //处理结果集 while (rs.next()){ String value = rs.getString("userId")+", "+rs.getString("userName"); System.out.println(value); } } catch (SQLException throwables) { throwables.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); }finally { //关闭连接 if (rs != null) { try { rs.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } if (stmt != null) { try { stmt.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } if (conn != null) { try { conn.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } } } }五步完整代码public class Insert { public static void main(String[] args) { Connection conn = null; Statement statement = null; try { // 1. 注册驱动 DriverManager.registerDriver(new Driver()); // 2. 获取连接 String url = "jdbc:mysql://127.0.0.1:3306/userdb?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Hongkong&allowPublicKeyRetrieval=true"; String user = "root"; String password = "123456"; conn = DriverManager.getConnection(url,user,password); // 3. 获取数据库操作对象 statement = conn.createStatement(); // 4. 执行sql语句 String sql = "insert into userinfo(userId, userName, gender, roleId, userPassword) values('user10', 'achong', 1,1,'123123')"; int count = statement.executeUpdate(sql); System.out.println(count); System.out.println(count==1?"成功":"失败"); } catch (SQLException throwables) { throwables.printStackTrace(); }finally { // 6. 释放资源 if (statement != null){ try { statement.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } if (conn != null){ try { conn.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } } } }
2022年04月30日
45 阅读
0 评论
0 点赞
2022-04-27
Servlet 浏览器响应文件下载功能实现
设置被下载文件先设置文件的下载地址获取文件名设置响应设置响应头设置文件流字节流,更好的传输文件。protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //1.获取内部文件的下载路径 String realPath = "D:\\study\\JavaWeb\\IDEA code\\test111\\ResponseDownload\\src\\com\\achong\\resources\\哪吒.png"; //2.获取下载的文件名(截取文件路径) String fileName = realPath.substring(realPath.lastIndexOf("\\")+1); //3.设置浏览器能够支持下载,注意处理文件名带中文 //先处理文件中文名问题 fileName = URLEncoder.encode(fileName, "UTF-8"); //告诉浏览器要下载该文件(设置响应头) response.setHeader("Content-Disposition", "attachment; filename="+fileName); //4.获取下载文件的输入流 FileInputStream in = new FileInputStream(realPath); //5.创建缓冲区 int len = 0; byte[] buffer = new byte[1024]; //6.获取OutputStream对象 ServletOutputStream out = response.getOutputStream(); //7.将FileOutputStream流写入buffer缓冲区,使用OutputStream将缓冲区中的数据输出到客户端 while ((len = in.read(buffer))>0){ out.write(buffer,0,len); } //最后,关闭流 in.close(); out.close(); }
2022年04月27日
39 阅读
0 评论
0 点赞
2022-04-11
学习的第一个注解@WebServlet - JavaWeb
@WebServlet@WebServlet的适当使用可以在一定程度上替代web.xml文件name属性:用来指定Servlet的名字,等同于<servlet-name>vlaue属性:用来指定Servlet的映射路径,可以指定多个字符串,等同于<url-pattern>urlPatterns属性:用来指定Servlet的映射路径,可以指定多个字符串,等同于<url-pattern>loadOnStartUp属性:用来指定在服务器启动阶段是否加载该Servlet,等同于<load-on-startup>注意:当注解的属性是一个数组,且数组中只有一个元素,大括号可以省略。@WebServlet(value="/hello")如果注解的属性名是value的话,属性名也是可以省略的。@WebServlet("/hello") 3.value和urlPatterns的作用是一样的。@WebServlet 源码// // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) // package javax.servlet.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface WebServlet { String name() default ""; String[] value() default {}; String[] urlPatterns() default {}; int loadOnStartup() default -1; WebInitParam[] initParams() default {}; boolean asyncSupported() default false; String smallIcon() default ""; String largeIcon() default ""; String description() default ""; String displayName() default ""; }
2022年04月11日
74 阅读
0 评论
0 点赞
2022-03-23
Mysql常用基础语句
持续更新......选择某表;USE test01;创建表后,添加主键约束;ALTER TABLE `user` ADD CONSTRAINT pk_id PRIMARY KEY(`id`);删除主键约束,不需要指定字段;ALTER TABLE `person1` DROP PRIMARY KEY;添加联合主键;ALTER TABLE `person1` ADD PRIMARY KEY (`uid`,`pid`);创建表后,添加外键约束;alter table 从表 add [constraint] 外键名称 foreign key (从表外键字段名) references 主表(主表的主键);ALTER TABLE `userInfo` ADD CONSTRAINT userInfo_user_fk FOREIGN KEY(`id`) REFERENCES `user`(`id`);删除外键约束,需要指定外键名称;ALTER TABLE `userInfo` DROP FOREIGN KEY userInfo_user_fk;增---------------------------------------------------------------------;创建表;CREATE TABLE person1( `uid` CHAR(12) NOT NULL, `pid` CHAR(12) NOT NULL, `text` CHAR(50) );插入数据;INSERT INTO user1(NO,NAME) VALUE ("125","achong");插入多行数据;INSERT INTO user1(NO,NAME) VALUE ("125","achong1"),("126","achong2"),("127","achong3"); INSERT INTO userInfo(id, address, phone) VALUE ("125","广东省清远市111",10001), ("126","广东省清远市222","10002"), ("127","广东省清远市333","10003");添加字段;ALTER TABLE user1 ADD `phone`CHAR(11);创建数据库create database userdb;删--------------------------------------------------------------------;删除某表;DROP TABLE user22;删除某行;DELETE FROM person1 WHERE `text`= "qwe00";删除表中所有行对比delete,truncate table的执行熟读更快,使用的系统资源和事务日志资源更少,并且删除数据后的标识列会重新开始编号。truncate table t_user;改--------------------------------------------------------------------;修改表名;ALTER TABLE `user1` RENAME `user`;修改字段,连同字段的属性都需要修改;ALTER TABLE user1 CHANGE `phone` `sex`CHAR(1);更新表数据update dept set loc='SH' where deptno=30;查--------------------------------------------------------------------;查询所有数据库;SHOW DATABASES; 查看所有表;SHOW TABLES;查看表结构;DESC `userInfo`;查询表数据;SELECT * FROM `userInfo`; SELECT * FROM `person1`;
2022年03月23日
46 阅读
0 评论
0 点赞
2022-03-17
HTTP请求和响应的分析
get and post准备:index.html, GetHttp.java,PostHttp.javaindex.html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>GET请求</h1> <form action="/test01/GetHttp" method="get"> userName<input type="text" name="userName"><br> passWord <input type="password" name="passWord"><br> <input type="submit" value="login"> </form> <br><br> <h1>post请求</h1> <form action="/test01/PostHttp" method="post"> userName <input type="text" name="userName"><br> passWord <input type="password" name="passWord"><br> <input type="submit" value="login"> </form> </body> </html>GetHttp@Override public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException { response.setContentType("text/html"); PrintWriter out = response.getWriter(); out.print("<!doctype html>"); out.print("<html>"); out.print(" <head>"); out.print(" <title>Form Get Servlet</title>"); out.print(" </head>"); out.print(" <body>"); out.print(" <h1>"); out.print(" from get servlet"); out.print(" </h1>"); out.print(" </body>"); out.print("</html>"); }PostHttp@Override public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException { response.setContentType("text/html"); PrintWriter out = response.getWriter(); out.print("<!doctype html>"); out.print("<html>"); out.print(" <head>"); out.print(" <title>Form Get Servlet</title>"); out.print(" </head>"); out.print(" <body>"); out.print(" <h1>"); out.print(" from get servlet"); out.print(" </h1>"); out.print(" </body>"); out.print("</html>"); }分析:响应HTTP具体报文HTTP/1.1 200 ok //状态行 Content-Type: text/html;charset=ISO-8859-1 //响应头 Content-Length: 166 Date: Thu, 17 Mar 2022 04:22:32 GMT Keep-Alive: timeout=20 Connection: keep-alive //空白行 <!doctype html> //响应体 <html> <head> <title>Form Get Servlet</title> </head> <body> <h1> from get servlet </h1> </body> </html状态行三部分组成第一部分:协议版本号(HTTP/1.1)第二部分:状态码(HTTP协议中规定的响应状态号。不i痛的响应结果对应不同的号码)200表示响应成功。404表示资源不存在。要么是没有该资源,要么是路径写错了。405表示前端发生的请求方式和后端的处理方式不一致时发生。比如前段是发送post请求,后端是get方式处理。比如前端是发生post请求,后端是get方式处理。500表示服务器端的程序出现了异常,一般是服务器端导致的错误。小总结:以4开头,一般是浏览器端的错误导致的。以5开头,一般是服务器端的错误导致的。第三部分:状态的描述ok表示正常成功结束not found表示资源找不到响应头:响应的内容类型响应的内容长度响应时间......空白行:用来分隔”响应头“和“响应体”的。响应体:就是响应的正文,响应的正文被浏览器渲染,解释并执行,最终展现出的效果。分析:请求get请求内容Request URL: http://localhost:8080/test01/GetHttp?userName=lisi&passWord=123 //请求行 Request Method: GET //请求头 Status Code: 200 Remote Address: [::1]:8080 Referrer Policy: strict-origin-when-cross-origin HTTP/1.1 200 Content-Type: text/html;charset=ISO-8859-1 Content-Length: 188 Date: Thu, 17 Mar 2022 04:44:40 GMT Keep-Alive: timeout=20 Connection: keep-alive GET /test01/GetHttp?userName=lisi&passWord=123 HTTP/1.1 Host: localhost:8080 Connection: keep-alive sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="99", "Google Chrome";v="99" sec-ch-ua-mobile: ?1 sec-ch-ua-platform: "Android" Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.74 Mobile Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Sec-Fetch-Site: same-origin Sec-Fetch-Mode: navigate Sec-Fetch-User: ?1 Sec-Fetch-Dest: document Referer: http://localhost:8080/test01/pages/http/http.html Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7 Cookie: Idea-4bb2b176=f38436c5-5137-4805-8d0a-75f83df8839d //空白行 //请求体post请求内容POST /test01/PostHttp HTTP/1.1 //请求行 Host: localhost:8080 //请求头 Connection: keep-alive Content-Length: 28 Cache-Control: max-age=0 sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="99", "Google Chrome";v="99" sec-ch-ua-mobile: ?1 sec-ch-ua-platform: "Android" Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.74 Mobile Safari/537.36 Origin: http://localhost:8080 Content-Type: application/x-www-form-urlencoded Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Sec-Fetch-Site: same-origin Sec-Fetch-Mode: navigate Sec-Fetch-User: ?1 Sec-Fetch-Dest: document Referer: http://localhost:8080/test01/pages/http/http.html Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7 Cookie: Idea-4bb2b176=f38436c5-5137-4805-8d0a-75f83df8839d //空白行 userName: achong //请求体 passWord: 123分析请求行包括三部分第一部分:请求方式,常用的有两个getpost第二部分:URI什么是URI?统一资源标识符。代表网络中某个资源的名字。但是无法通过URI定位资源什么是URL?统一资源定位符。代表网络中某个资源。可以通过URL定位该资源。URI和URL的关系,区别URL包含URI。http://localhost:8080/test01/pages/http/http.html是URL/test01/pages/http/http.html是URI第三部分:HTTP协议版本号。请求体请求的主机主机的端口浏览器信息平台信息Cookie等等空白行分隔”请求体“和”请求体“。请求体向服务器发送的具体数据。get和post的区别怎么向服务器发送GET请求,怎么向服务器发送POST请求?到目前为止,只有一种情况可以发送POST请求:使用form表单,且form标签中的method属性值为“post”。其他情况一律是GET请求。在浏览器上直接输入URL回车,属于get请求。在浏览器直接点击超链接,属于get请求。使用form时,from标签中的method值为get。......GET请求和POST请求的区别get请求发送数据时,数据会挂在URL后面,以” ?“隔开,” ?“后面是数据。这样会导致数据显示在浏览器地址栏上。post请求发送数据的时候,在请求体中发送,不会显示在浏览器地址栏。get请求只能发送普通字符,并且字符串的长度有限制,不同浏览器现在不同,没有明确规范。get无法发送大量数据。post请求可以发送任何类型的数据,包括普通字符,流媒体信息,图片...post可以发送大量数据,理论上没有上限。get请求在W3C中是这样说的,get请求比较适合从服务器段获取数据。post请求在W3C中时这样说的:post请求比较适合向服务器端传送数据。get和post的安全性。get请求是安全的。为什么?从应用场景来说,get请求只是为了从服务器获取数据。不会对服务器造成威胁。post请求时危险的。为什么?因为post时向服务器提交数据,如果这些数据通过后门的方式进入到服务器当中,服务器是很危险的。一般情况下。拦截/监听的请求,大部分是post有些情况,比如get请求会使信息暴露在浏览器地址栏,这是因为使用了错误的请求方式造成的。使用规范请回看上面的7、8。get请求支持缓存。任何一个get请求都会被浏览器缓存起来。https://n.sinaimg.cn/default/590/w240h350/20220316/3a70-d24bfc7f073a3b02bbb1fef5aa17ccd4.jpg在浏览器缓存中,一个资源对应一个路径实际上,在发送get请求的时候,浏览器第一时间是在本地查找资源,找不到了才会去服务器获取。如何不让get走缓存?http:abc.com/img/1.jpg?t=1231234http:abc.com/img/1.jpg?t=1231234http:abc.com/img/1.jpg?t=系统毫秒数只要每次请求的路径不同即可,可以在文件末尾加上时间戳。post请求不支持缓存。因为post请求的一般是动态数据。get和post如何选择?看使用场景。如果想向服务器发送数据,建议get。如果想获取服务器上的资源,建议post。大部分表单提交都是post,因为form表单中需要填写大量数据,一般是要传给服务器来保存或修改的。表单中有敏感数据,建议使用post,因为get会回显数据在浏览器地址栏。做文件上传,一定是要post请求。get和post发送的数据格式时完全相同的,只不过时位置不一样。userName=lisi&passWord=123name是什么?以form表单为例:from表单中的input标签的name。value是什么?以form表单为例:from表单中的input标签的value。
2022年03月17日
36 阅读
0 评论
0 点赞
2022-03-13
解决Tomcat服务其在DOS命令窗口的乱码问题(控制台乱码)
解决前的DOS界面:解决后的DOS界面:解决方法:只需修改小猫的apache-tomcat-8.5.75conflogging.properties这份文件里的43行代码。将原来的编码UTF-8改成GBK即可。因为DOS界面是GBK的编码方式。
2022年03月13日
62 阅读
0 评论
0 点赞
2022-03-11
可变长度参数
可变参数可变长度参数作出现在方法形参列表中。可变长度参数要求的参数个数是:0 ~ N 个;普通参数和可变长度参数可同时出现在修饰符列表。可变长度参数在参数列表中必须在最后一个,且只能有一个。可变长度参数可以当作一个数组来看1.只有可变长度参数 全部参数都会赋给可变长度参数a。public static void main(String[] args) { sum(1,2,3,4,5,6,7,8,9,10); } public static void sum(int...a){ int sum = 0; for (int j : a){ sum+=j; } System.out.println(sum); }2.当普通参数和可变长度参数同时出现 在传进去的参数里,1会赋给普通参数b,2会赋给普通参数c,剩下的全部赋给可变长度参数a。public static void main(String[] args) { sum(1,2,3,4,5,6,7,8,9,10); } public static void sum(int b,int c, int...a){ int sum = 0; sum = sum + (b + c); for (int i : a){ sum += i; } System.out.println(sum); }
2022年03月11日
49 阅读
0 评论
0 点赞
2022-03-06
深究contains方法
contains方法解析在java中,contains方法用于判断集合中是否包含某个元素,包含则返回true,否则返回false。boolean contains(Object o); //先看一个案例:字符串str实际上没添加进集合c里,但是contains返回的结果却是true?也就是说,contains发现str包含在c集合里。public static void main(String[] args) { //创建集合对象 Collection c = new ArrayList(); //添加数据 c.add("abc"); c.add(123); //1.str实际上没添加进集合c里,但是contains返回的结果却是true? String str = "abc"; System.out.println("str是否包含在集合里:"+c.contains(str)); //true 再看第二个案例:new了两个属性相同的学生对象stu1和stu2。跟代码一逻辑一样,把stu1添加进集合里,因为两个学生的名字是相同的,所以用contains方法比较stu2是否包含在集合c里,然而这次返回结果是false,为什么,明明两个学生名字一样啊。public static void main(String[] args) { //创建集合对象 Collection c = new ArrayList(); //添加数据 c.add("abc"); c.add(123); //2.根据上面的逻辑,这次new两个属性相同的学生对象stu1和stu2,把stu1添加进集合里,然后判断stu2是否在集合里,这次结果为false Student stu1 = new Student("小明"); Student stu2 = new Student("小明"); c.add(stu1); System.out.println("学生对象stu是否在集合c里:"+c.contains(stu2)); //false } } //学生类 class Student{ String name; //带参构造方法 public Student(String name) { this.name = name; }原因: 通过查看实现类ArrayList里的contains方法可以发现,从contains()到indexOf(),再到indexOfRange(),在最后的indexOfRange()方法里,调用了equals()方法来进行比较。//contains的底层源码 public boolean contains(Object o) { return indexOf(o) >= 0; } public int indexOf(Object o) { return indexOfRange(o, 0, size); } int indexOfRange(Object o, int start, int end) { Object[] es = elementData; // if (o == null) { for (int i = start; i < end; i++) { if (es[i] == null) { return i; } } } else { for (int i = start; i < end; i++) { if (o.equals(es[i])) { return i; } } } return -1; }结论分析 关于equals方法,我们得知道,所有的子类默认继承Object类,而Object的equels方法只是返回对象内存地址。 由此分析代码一:集合里有一个字符串“abc”,创建一个String变量赋值“abc”,两者本质上都是字符串,且String类型已经重写equals方法。contains在对两个比较的时候调用的是equals方法,由于String类的已经重写了,返回的是属性而不是内存地址,故字符串可以被正确比较。 代码二分析:虽然两个学生的名字属性一样,但是,集合保存的是对象的内存地址,而Student类没有重写equals方法,所以contains方法在调用equals方法判断的时候,判断的是两个对象的地址,两个对象的内存地址肯定不一样,所以返回false。 代码二解决方案:重写Student的equals方法,使其返回对象属性,即可获取正确的判断。//学生类重写的equals方法 class Student{ String name; public Student(String name) { this.name = name; } @Override public boolean equals(Object obj) { if (obj == null || !(obj instanceof Student)) return false; if(obj == this) return true; Student s = (Student) obj; if(this.name == s.name) return true; return false; } }再看以下代码:Integer x = 1000; Integer y = 1000; c.add(x); System.out.println(c.contains(y)); //true虽然new了两个不一样的对象,但是Integer包装类重写了equals方法,所以x和y是一样的。结论:放在集合里的类型,一定要重写equals方法。注意:remove删除方法也是调用的equals方法。
2022年03月06日
101 阅读
0 评论
1 点赞
2022-03-05
Collection集合里的迭代器
Collection集合的迭代器迭代器适合所有集合。常用的两个函数:boolean hasNext(); //判断集合里还有无元素。 next(); //这个方法让迭代器前进一位,并且将指向的元素返回。 void remove(); //删除当前元素一般这两个方法会一起使用,先hasNext()判断集合是否还有元素,然后next()拿到下一个元素。public static void main(String[] args) { //创建一个集合对象 Collection c = new ArrayList(); //添加数据 c.add("你好"); c.add("我叫阿冲"); c.add("很高兴认识你"); c.add("你叫什么名字?"); c.add(123); c.add("abc"); // 创建集合对象 Iterator it = c.iterator(); //循环判断,并取出。 while(it.hasNext()){ Object obj = it.next(); //这里用Object类型接收,因为集合里存储的对象类型可能不一样 System.out.println(obj); } }迭代集合原理该方法拿到一个迭代器,它负责遍历集合中的元素。it.hasNext(); 这个方法返回true,表示集合里还有元素可以迭代,若返回false,则表示没有更多的元素可以迭代了。it.next();表示拿到迭代器当前位置的下一个元素并返回。把集合想象成一个数组,迭代器像一个指向集合里元素的箭头。起初,迭代器指向的位置是在第一个元素的前一位。it.next()每拿一个元素,迭代器就前进一位Iterator it = c.iterator(); while(it.hasNext()){ Object obj = it.next(); //这里用Object接收,因为集合里存储的对象类型可能不一样 System.out.println(obj); }迭代器remove方法先说结论:如果集合的状态发生改变,对应迭代器必须重新获取。若集合的状态改变了,但是迭代器没有更新,此时还用旧的迭代器输出数据,就会发生异常。例如以下代码: 在拿到迭代器后,又往集合里添加一个新数据,此时下面的循环遍历就会报异常 ConcurrentModificationExceptionCollection c = new ArrayList(); c.add(123); c.add(234); c.add(234); Iterator it = c.iterator(); c.add(1122); while (it.hasNext()){ Object o = it.next(); System.out.println(o); }迭代器的原理: 在执行语句Iterator it = c.iterator();的时候,相对于把 c 集合里的数据复制了一份出来保存到 it 里。在使用点带起的时候,系统会时刻检查 c集合 和 it迭代器的内容是否一致 。 所以,往集合添加和删除元素的时候,需要及时更新迭代器;在迭代的过程中需要删除元素,必须使用迭代器的remove方法,而不是集合的remove方法。
2022年03月05日
52 阅读
0 评论
0 点赞
2022-03-05
Collection接口里的常用方法
Collection接口的常用方法Collection是接口,以下使用该接口的实现类来测试该接口里的方法。下面只是列举了一些常用的方法。Collection中能存放什么元素? 没有使用”泛型“之前,Collection可以存储Object的所有子类型。使用”泛型“之后,Collection只能存储某个数据类型。 集合中不能直接存储数据类型,也不能存储java对象,只是 存储java对象的内存地址。 常用方法 //往集合里添加元素。boolean add(Object e); //获取集合中元素个数。 int size(); //清空集合。 void clear(); //判断集合是否包含某元素。 boolean contains(); //删除集合中某个元素 boolean remove(Object o; //判断该集合中元素个数是否为0,即是否为空。 boolean isEmpty(); //把集合转换成数组。Object[] toArray();
2022年03月05日
65 阅读
0 评论
0 点赞
2022-03-05
多线程概述
多线程什么是进程?什么是线程?进程是一个运行中的应用程序。线程时进程中的执行场景/执行单元一个进程可以启动多个线程进程之间的内存独立不共享QQ是一个进程,微信是一个进程,这两个进程时独立的,不共享资源线程之间线程A和线程B,堆方法和方法区内存共享,但是栈内存独立,一个线程一个栈假设启动10个线程,就会有10个栈空间,每个栈和每个栈之间互不干扰,各自执行各自的,这就是多线程并发假设进程是一个火车站,那每一个售票窗口就是一个线程,每个窗口之间的可以同时售票。所以多线程并发可以提高效率。线程的生命周期注意:使用了多线程机制后,main方法结束,程序也不会结束。mian方法结束只是主线程结束了,主栈空了,其他栈可能还在运行(压栈弹栈)
2022年03月05日
76 阅读
0 评论
0 点赞
2022-03-05
集合继承结构图
集合结构Collection- CollectionListList集合存储特点,元素有序可重复,存储的元素有下标。ArrayListArrayList集合底层采用了数组这种数据结构。ArrayList集合是非线程安全的。LinkListLinkList集合底层采用了双向链表数据结构VectorVector集合也是采用双向链表的数据结构,但它是线程安全的。Vector所有的方法都有synchronized关键字修饰,所以线程安全,但是效率较低。选择保证线程安全有别的办法,故Vector使用较少了。SetSet集合存储特点:元素无序不可重复。HashSet实际上HashSet集合在new的时候,底层实际上new了一个HashMap集合。向HashSet中存储元素,实际上是存储在HashMap集合中。HashMap集合是一个哈希表数据结构。TreeSetSet实现了SortedSet接口,TreeSet是一个实现类。TreeSet底层实际上是TreeMap,new TreeSet的时候,实际上是new了一个TreeMap集合。往TreeSet中存储数据的时候,实际上是将数据放到TreeMap集合中了。-TreeMap集合底层采用了二叉树结构。MapMap集合与Collection集合没有关系。Map集合以key和value的这种键对值的方式存储元素。key和value都是存储java对象的内存地址。 -所有Map集合的key特点:无序不可重复的。Map集合的key和Set集合的存储元素特点相同。HashMap:-HashMap集合底层是哈希表数据结构,是非线程安全的。Hashteble-Hashtable集合底层也是哈希表数据结构,是线程安全的,其中所有的方法都带有synchronized关键字,但效率较低,现在使用少了,因为控制线程安全有其他更好的方案。 -Properties -Properties是线程安全的,因为继承Hashtable,另外Properties存储元素的时候也是采用key和value的形式存储,并且key和value支支持String类型,不支持其他类型。 -Properties被称为属性类TreeMap <interface> SortedSetTreeMap集合底层的数据结构是一个二叉树。
2022年03月05日
42 阅读
0 评论
1 点赞
2022-03-05
欢迎使用 Typecho
如果您看到这篇文章,表示您的 blog 已经安装成功.
2022年03月05日
30 阅读
1 评论
0 点赞
粤ICP备18061175号-3