暂时未有相关云产品技术能力~
[1] 总览 Java Stream是一种基于流式处理的API,它提供了一种简洁而高效的方式来处理集合、数组或任何其他数据源中的元素。在使用Java Stream时,可以通过链式调用一系列的中间操作和终端操作来对数据源中的元素进行处理和转换,而不需要显式地使用循环或条件语句。 Java Stream主要分为两种类型:中间操作和终端操作。中间操作是在数据源上进行的转换操作,每次操作都会返回一个新的Stream实例,以便继续进行操作。而终端操作是指对Stream进行最终操作的操作,如收集、计算或迭代等。1)中间操作,可以有多个,每次返回一个新的流,可进行链式操作。2)终端操作,只能有一个,每次执行完,这个流就结束了,因此只能放在最后。 一些常见的Java Stream操作: · 过滤操作(filter):使用给定的谓词过滤数据源中的元素。 · 映射操作(map):将数据源中的每个元素映射为新元素。 · 排序操作(sorted):按指定的顺序对数据源中的元素进行排序。 · 去重操作(distinct):从数据源中删除重复的元素。 · 统计操作(count、min、max、sum、average):统计数据源中的元素。 · 收集操作(collect):将数据源中的元素收集到一个集合中。 · 迭代操作(forEach):对数据源中的每个元素进行迭代操作。 · 匹配操作(allMatch、anyMatch、noneMatch):使用给定的谓词测试数据源中的元素是否匹配。 · 并行流操作(parallelStream):使用并行处理加速处理数据源中的元素。 Java Stream可以使程序员更加简洁、高效地处理数据,使我们的代码更加易于阅读和维护。它还提供了许多其他功能,如支持延迟计算、内部迭代等。在实际的开发中,Java Stream可以广泛应用于集合处理、数据筛选和转换等场景。[2] 代码演示[2.1] 过滤操作(filter) 中间操作 这个例子中,首先创建了一个字符串列表words。接着,使用stream()方法将列表转换为一个Stream对象。然后,使用filter()方法来过滤出长度大于5的元素,得到了新的Stream对象。最后,使用collect()方法将过滤后的结果收集到一个List中,并输出结果[banana, orange]。List<String> words = Arrays.asList("apple", "banana", "orange", "pear", "grape"); List<String> longWords = words.stream() .filter(word -> word.length() > 5) .collect(Collectors.toList()); System.out.println(longWords); // [banana, orange] 这里使用了collect把stream流转化为List。[2.2] 映射操作(map) 中间操作 在这个例子中,首先创建了一个整数列表numbers。然后,使用stream()方法将列表转换为一个Stream对象。接着,使用map()方法将每个元素映射为它的平方,并得到新的Stream对象。最后,我们使用collect()方法将映射后的结果收集到一个List中,并输出结果[1, 4, 9, 16, 25]。List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); List<Integer> squares = numbers.stream() .map(n -> n * n) .collect(Collectors.toList()); System.out.println(squares); // [1, 4, 9, 16, 25] [2.3] 排序操作(sorted) 中间操作 这个例子中,首先创建了一个字符串列表words。然后,使用stream()方法将列表转换为一个Stream对象。接着,使用sorted()方法将元素按字典序排序,并得到新的Stream对象。最后,使用collect()方法将排序后的结果收集到一个List中,并输出结果[apple, banana, grape, orange, pear]。List<String> words = Arrays.asList("apple", "banana", "orange", "pear", "grape"); List<String> sortedWords = words.stream() .sorted() .collect(Collectors.toList()); System.out.println(sortedWords); // [apple, banana, grape, orange, pear][2.4] 去重操作(distinct) 中间操作 在例子中,使用distinct()方法去除了列表中的重复元素。List<String> names = Arrays.asList("John", "Jane", "John", "Mike", "Mike"); List<String> distinctNames = names.stream().distinct().collect(Collectors.toList()); System.out.println(distinctNames); // 输出:[John, Jane, Mike][2.5] 统计操作(count、min、max、sum、average) 终端操作 在例子中,使用了count()方法计算列表中元素的数量,max()方法获取最大值,min()方法获取最小值,sum()方法求和,average()方法求平均值。List<Integer> numbers = Arrays.asList(2, 4, 6, 8, 10); long count = numbers.stream().count(); int max = numbers.stream().max(Integer::compareTo).orElse(0); int min = numbers.stream().min(Integer::compareTo).orElse(0); int sum = numbers.stream().mapToInt(Integer::intValue).sum(); double average = numbers.stream().mapToDouble(Integer::intValue).average().orElse(0.0); System.out.println("Count: " + count); // 输出:Count: 5 System.out.println("Max: " + max); // 输出:Max: 10 System.out.println("Min: " + min); // 输出:Min: 2 System.out.println("Sum: " + sum); // 输出:Sum: 30 System.out.println("Average: " + average); // 输出:Average: 6.0 stream().max(Integer::compareTo)是使用Java Stream对整数流进行求最大值操作的一种方式。stream()表示将集合或数组转换成一个流;而max()是一个终止操作,会返回流中的最大值。 在这个例子中,我们使用了方法引用Integer::compareTo,它表示使用Integer类的compareTo方法来进行元素的比较。compareTo方法是Comparable接口中定义的方法,用于比较两个整数的大小,返回一个整数值,如果第一个整数小于第二个整数,返回负数;如果两个整数相等,返回0;如果第一个整数大于第二个整数,返回正数。 因此,stream().max(Integer::compareTo)会返回整数流中的最大值,如果流为空,则返回Optional.empty()。如果想获取最小值,可以使用min()方法,使用方式类似。 使用stream流求和的方法通常是先将流中的元素映射为整数类型,然后使用sum()方法对整数流进行求和。在这里,mapToInt(Integer::intValue)方法用于将流中的元素转换为整型,并返回一个新的IntStream类型的流。因为IntStream是一个基本类型的流,因此它具有更高的效率和更低的内存消耗。[2.6] 收集操作(collect) 终端操作 在例子中,使用了collect()方法将列表中的元素连接成一个字符串,使用joining()方法指定连接符。List<String> names = Arrays.asList("John", "Jane", "Mike"); String joinedNames = names.stream().collect(Collectors.joining(", ")); System.out.println(joinedNames); // 输出:John, Jane, Mike Collectors类实现了很多归约操作,例如将流转换成集合和聚合元素。Collectors常用于返回列表或字符串:.collect(Collectors.toList()) // 返回列表 .collect(Collectors.joining(", ") // 返回字符串[2.7] 迭代操作(forEach) 终端操作 在例子中,使用了forEach()方法对列表中的每个元素执行一些操作,这里是打印一个问候语。List<String> names = Arrays.asList("John", "Jane", "Mike"); names.stream().forEach(name -> System.out.println("Hello, " + name));[2.8] 匹配操作(allMatch、anyMatch、noneMatch) 终端操作 allMatch:判断数据源中所有的元素是否都满足给定的条件,如果全部满足则返回true,否则返回false。List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); boolean allMatch = numbers.stream().allMatch(n -> n > 0); // true anyMatch:判断数据源中是否有任何一个元素满足给定的条件,如果有则返回true,否则返回false。List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); boolean anyMatch = numbers.stream().anyMatch(n -> n > 4); // true[2.9] 并行流操作(parallelStream) 并行流操作是Java Stream提供的一种并行处理数据源中元素的方式。与串行流不同,当使用并行流时,Stream会将数据源分成多个部分,并在多个线程上同时处理这些部分,以加速数据处理的速度。 在例子中,首先将一个包含5个整数的列表转换为一个Stream对象,然后调用parallelStream()方法将该Stream对象转换为并行流对象。接着,使用filter操作筛选出列表中的偶数,最后使用count操作计算符合条件的元素的数量。 使用并行流操作可以加速数据处理的速度,但也需要注意并发安全和线程安全等问题,确保在使用并行流时能够正确处理数据。List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); long count = numbers.parallelStream().filter(n -> n % 2 == 0).count(); // 2[3] 关于mapToxxx Java Stream API中的mapToxxx()方法可以将Stream中的每个元素映射为一个新的值,然后返回一个新的Stream。其中xxx代表了一个基本类型,如Int,Long或Double。 以下是mapToxxx()方法的一些常见用法: 1. mapToInt(): 将Stream中的每个元素映射为一个int值,并返回一个新的IntStream。 例如,将一个字符串列表中的所有字符串的长度映射为一个int值的示例代码如下:List<String> strings = Arrays.asList("Java", "Stream", "API"); IntStream lengths = strings.stream().mapToInt(String::length); 2. mapToLong(): 将Stream中的每个元素映射为一个long值,并返回一个新的LongStream。 例如,将一个数字列表中的每个数字乘以2,然后将结果映射为一个long值的示例代码如下:List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); LongStream doubledNumbers = numbers.stream().mapToLong(n -> n * 2L); 3. mapToDouble(): 将Stream中的每个元素映射为一个double值,并返回一个新的DoubleStream。 例如,将一个数字列表中的每个数字除以2,然后将结果映射为一个double值的示例代码如下:List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); DoubleStream halfNumbers = numbers.stream().mapToDouble(n -> n / 2.0); 需要注意的是,由于基本类型无法为null,因此如果在映射过程中存在可能返回null的情况,需要先使用filter()方法过滤掉这些元素,以避免空指针异常的出现。
写法非常简单前端Vue页面 1. 在表格上加监听函数@sort-change; 2. 在表行上加排序字:sort-orders、可排序字sortable; 3. 实现监听函数。 代码如下:<el-table v-loading="loading" :data="equipList" @selection-change="handleSelectionChange" @sort-change="handleSortChange"> <el-table-column label="创建时间" align="center" prop="createTime" width="160" :sort-orders="['descending','ascending']" sortable="custom">handleSortChange(column) { this.queryParams.orderByColumn = column.prop;//查询字段是表格中字段名字 this.queryParams.isAsc = column.order;//动态取值排序顺序 this.getList(); } 后端XML代码 在selectList里面加上参数解析即可,如下:实现原理 若依框架后端里面已经封装好对应的解析代码了:
一定要弄懂项目部署的方方面面。当服务器上部署的项目过多时,端口号什么时候该放行、什么时候才会发生冲突?多个项目使用redis怎么防止覆盖?Nginx的配置会不会产生站点冲突?二级域名如何合理配置?空闲的时候要自己用服务器试试,不然书到用时方恨少,项目紧急时赶不出来……[0] 部署SpringBoot前后端不分离项目步骤 1. 阿里云控制台“云解析DNS”添加二级域名站点 (可选) 2. 阿里云控制台添加防火墙端口号、宝塔面板放行同样端口号 (可选) 3. 宝塔面板安装JDK、Nginx、MySQL 4. 导入数据库 5. 修改数据库连接账号密码,打包项目 6. 上传项目到指定位置,宝塔中添加Java站点[1] 阿里云控制台“云解析DNS”添加二级域名站点 (可选) 在阿里云控制台“云解析DNS”——“解析设置”中添加二级域名站点: 添加二级域名的话,只需要填写图中框中的地方即可。 为什么此步骤是可选的?因为有的人想要用一级域名(即www.xxxx.com)来访问站点,那么这里就不需要再来额外添加二级域名了。在DNS开始的时候,阿里云控制台就会解析几条域名数据,其中包含www。[2] 阿里云控制台添加防火墙端口号、宝塔面板放行同样端口号 (可选) 阿里云控制台放行防火墙端口号: 在宝塔面板同样放行防火墙端口号: 为什么此步骤是可选的?因为默认端口号80是放行的,可以直接使用80端口号。使用了nginx之后,多个站点可以使用同一个端口号。例如多个站点使用了80端口,一个请求过来时,nginx会监听80端口,然后根据不同站点的配置文件,把请求转发到不同位置。这就叫做反向代理。[3] 宝塔面板安装JDK、Nginx、MySQL 1、JDK是Java运行环境。 2、Nginx功能丰富,可作为HTTP服务器,也可作为反向代理服务器,邮件服务器。支持FastCGI、SSL、Virtual Host、URL Rewrite、Gzip等功能。并且支持很多第三方的模块扩展。 3、MySQL安装时要注意版本,一般是5.7或8.0。 以上三者直接去宝塔应用商店安装即可。[4] 导入数据库 宝塔面板中创建数据库: 创建好之后,把sql文件导入。[5] 修改数据库连接账号密码,打包项目 把SpringBoot项目中连接数据库文件的账号密码改成上图中的,然后把项目打包好成Jar包。[6] 上传项目到指定位置,宝塔中添加Java站点 上传项目到指定位置,这里的位置不太重要,随便那里都可以。 宝塔中添加Java站点: 提交完成后,直接访问可能会报错502,耐心等一小会,项目就会完整启动了。 nginx采用反向代理。我们用二级域名不加端口去访问,nginx将80端口获得的请求转发到nginx中的对应端口中,如上图设置的8752。【注意】 在阿里云“DNS解析”中添加放行端口时,有时间延迟,要耐心等一会;
一定要弄懂项目部署的方方面面。当服务器上部署的项目过多时,端口号什么时候该放行、什么时候才会发生冲突?多个项目使用redis怎么防止覆盖?Nginx的配置会不会产生站点冲突?二级域名如何合理配置?空闲的时候要自己用服务器试试,不然书到用时方恨少,项目紧急时赶不出来……[0] 部署域名访问PHP项目步骤 1. 阿里云控制台“云解析DNS”添加二级域名站点 (可选) 2. 阿里云控制台添加防火墙端口号、宝塔面板放行同样端口号 (可选) 3. 宝塔面板安装PHP、Nginx、MySQL 4. 导入数据库 5. 修改数据库连接账号密码,打包项目 6. 宝塔中添加站点,上传项目到指定位置[1] 阿里云控制台“云解析DNS”添加二级域名站点 (可选) 在阿里云控制台“云解析DNS”——“解析设置”中添加二级域名站点: 添加二级域名的话,只需要填写图中框中的地方即可。 为什么此步骤是可选的?因为有的人想要用一级域名(即www.xxxx.com)来访问站点,那么这里就不需要再来额外添加二级域名了。在DNS开始的时候,阿里云控制台就会解析几条域名数据,其中包含www。[2] 阿里云控制台添加防火墙端口号、宝塔面板放行同样端口号 (可选) 阿里云控制台放行防火墙端口号: 在宝塔面板同样放行防火墙端口号: 为什么此步骤是可选的?因为默认端口号80是放行的,可以直接使用80端口号。使用了nginx之后,多个站点可以使用同一个端口号。例如多个站点使用了80端口,一个请求过来时,nginx会监听80端口,然后根据不同站点的配置文件,把请求转发到不同位置。[3] 宝塔面板安装PHP、Nginx、MySQL 1、不安装PHP,.php后缀的文件无法解析。 2、Nginx功能丰富,可作为HTTP服务器,也可作为反向代理服务器,邮件服务器。支持FastCGI、SSL、Virtual Host、URL Rewrite、Gzip等功能。并且支持很多第三方的模块扩展。 3、MySQL安装时要注意版本,一般是5.7或8.0。 以上三者直接去宝塔应用商店安装即可。[4] 导入数据库 宝塔面板中创建数据库: 创建好之后,把sql文件导入。[5] 修改数据库连接账号密码,打包项目 把PHP项目中连接数据库文件的账号密码改成上图中的,然后把项目打包好。 不太严格的PHP项目的话,可以不打包,上传时直接传文件夹即可。[6] 宝塔中添加站点,上传项目到指定位置 宝塔中添加站点: 如果想用第1、2步中的二级域名和端口的话,一定要写:aaa.bbb.com:8054,写aaa.bbb.com就默认使用80端口。PHP版本记得选。 添加成功后,会在根目录生成一个指定域名的文件夹,把项目上传到该目录中就完成了。【注意】 1、在阿里云“DNS解析”中添加放行端口时,有时间延迟,要耐心等一会; 2、如果PHP项目访问不了,试一试把站点根目录下,宝塔生成的.user.ini文件删除。[7] 部署IP访问PHP项目步骤 1. 阿里云控制台添加防火墙端口号、宝塔面板放行同样端口号 (可选) 2. 宝塔面板安装PHP、Nginx、MySQL 3. 导入数据库 4. 修改数据库连接账号密码,打包项目 5. 宝塔中添加站点,上传项目到指定位置 很多时候我们都是没有域名的,部署用IP访问网站更简单,和部署域名访问PHP项目步骤,只不过不需要去添加域名了。在第5步“宝塔中添加站点”时,把域名换成你的ip,其他都不变。
[1] 问题的由来 在日常开发中,实体类需要序列化,一般写法如下:public class User implements java.io.Serializable { private static final long serialVersionUID = 1L; // 用户id private Long user_id; } 上述代码中,private static final long serialVersionUID = 1L;,数字1后面为什么要加L。 要想彻底搞懂这个问题,我们要先理解: 1、Java中整型直接量在没有加后缀的时候默认为int,但是当它被赋值给某个变量(这个变量是short型,int型,byte型中的一种)时,则会自动转化成相对应的类型; 2、Java的4个整数基本类型:byte->short->int->long; 3、向上转型、向下转型; 4、装箱、拆箱; 下面我们分情况讨论,定义长整型时,什么时候应该加L,什么时候可以不加L。[2] 用long定义长整型数字时long a = 1; // 类型int向上转型为long long a = 1L; // 类型直接定义为long long a = 2147483648; // 错误 int的最大表示范围是2147483647 long a = 2147483648L; // 正确 2147483648为长整型 上面四行代码: 1、由于Java默认数字是int类型的,而int向上转型为long是安全的,所以第一句正确执行; 2、数字后面加了l或L后,类型变为长整型,第二句自然没问题; 3、int能表示的数字有一定范围,超过这个范围必须加l或L才不会出错,所以第三句出错,第四句正确。【注】由第一行的代码可以联想到,为什么每次定义byte、short、int类型可以直接写,定义long类型要注意加L的情况。'[3] 用Long定义长整型数字时Long a = 1; // 错误 Long的自动装箱必须要求long类型的数字 Long a = 1L; // 正确 1L是long类型 自动装箱 Long a = new Long(1); // 正确 Long构造器的形参要求long类型的数字,int类型可以向上转型为long类型 Long a = new Long(1L); // 正确 1L是long类型 Long a = new Long(2147483648); // 错误 int的最大表示范围是2147483647,表示错误 Long a = new Long(2147483648L); // 正确 2147483648为长整型 上面六行代码: 1、自动装箱严格要求对应的基本类型要一致,所以第1行错误第2行正确; 2、使用构造器创建Long类型数字时,形参是long类型,int类型可以去向上转型,所以第3、4行正确; 3、使用构造器时,必须注意int不可以超过范围。[4] 综上可得两种必须加L的情况 1、使用long和new Long()定义时,当数字超过int类型的表示范围时必须要在数字后加L; 2、使用Long定义时,数字必须要加L。
·请参考本系列目录:【BERT-多标签文本分类实战】之一——实战项目总览·下载本实战项目资源:>=点击此处=<[1] BERT模型库 从BERT模型一经Google出世,到tensorflow与pytorch版本的BERT相继发布,再到不同下游任务的BERT模型被整合到transformers库,到底pip哪个库才能用BERT对于刚入门的同学来说是非常的不友好。 其实大部分的人的需求都非常地简单:只想用BERT模型替代原始的嵌入层来得到文本表示。大部分的人更在乎的是如何把bert当作和embedding层一样的黑箱,输入文本,得到文本表征向量。 这里,本项目使用的是Hugging Face的transformers库,因为它整合的太棒啦!transformers库里面有NLP里面很火的大模型,比如BERT、ALBERT、GPT、GPT2、XLNET等等。本项目只用到了transformers.BERT,其文档地址为:https://huggingface.co/docs/transformers/model_doc/bert。 本篇文章主要还是以实战为导向,简单说一下transformers.BERT要怎么用。想要深入使用,大家仔细看看上面贴的文档,写得非常好。[2] transformers.BERT的下载 其实在自己的模型里使用BERT非常简单,并不会像其他教程一样与transformers库耦合的很深,本次在数据集Reuters-21578上进行多标签文本分类的实战项目,就是在之前单标签文本分类任务上改动的。单标签文本分类实战项目,请参考:【BERT-多标签文本分类实战】之一——实战项目总览[2.1] 使用BERT前的准备 使用BERT需要2个前置条件: 1、安装transformers库; 2、下载预训练参数文件。 第一点不必赘述。第二点可说的比较多。首先,为什么要下载预训练参数文件?BERT它是预训练模型,有自己的网络结构,而且里面的参数达到上亿级别,BERT在大规模无监督语料上训练得到最终参数之后,是要把参数保存下来,之后有新的数据集要跑的时候,我们需要把这些参数数据载入到模型。除此之外,光有预训练参数文件不够,还需要一个配置文件,来告诉它模型的很多配置是多少(例如词典有多大、隐藏层维度是几等等)。最后还需要一个txt文件来存放词典。 总结一下,BERT的预训练参数文件里有3个文件:1)参数数据;2)模型配置信息;3)txt词典。[2.2] 预训练参数文件的挑选与下载 BERT的参数文件有很多个版本: 上面框出来的是我比较常用的两个。bert-base-uncased表示不区分大小写的英文模型,bert-base-chinese表示中文模型。 假设我们要下载bert-base-uncased,我们到页面:https://huggingface.co/bert-base-uncased/tree/main,下载的文件已经在图中框出: 由于本次实战使用的是pytorch框架,所以最终我们下载下来的文件是:【注】只下载这三个就够了,其他都可以不下载。[3] transformers.BERT的测试示例 我们对BERT模型的需求是,利用它来编码句子。在我们的实战项目中,有一个测试.py文件,里面展示了BERT的基本用法。 把下载下来的参数文件放在项目下,然后写代码开始测试:import torch from transformers import BertModel, BertTokenizer pretrained_path = 'bert-base-uncased' # 从文件夹中加载bert模型 model = BertModel.from_pretrained(pretrained_path) # 从bert目录中加载词典 tokenizer = BertTokenizer.from_pretrained(pretrained_path) # 输出字典的大小 print(f'vocab size :{tokenizer.vocab_size}') # 把'[PAD]'编码 print(tokenizer.encode('[PAD]')) # 把'[SEP]'编码 print(tokenizer.encode('[SEP]')) 输出:vocab size :30522[101, 0, 102][101, 102, 102] 接下来对英文句子进行词典编码,这里主要是把英文句子,按照词典里面的词序进行转化:# 把句子编码,默认加入了special tokens了,也就是句子开头加入了[CLS] 句子结尾加入了[SEP] ids = tokenizer.encode("I love you transport", add_special_tokens=True, padding='max_length', truncation='only_first', max_length=6) print(ids)输出:[101, 1045, 2293, 2017, 3665, 102]【注】不要凌乱! 来总结一下到目前为止说了什么:第一小节介绍了采用transformers库来使用BERT;第二小节介绍了使用BERT要下载哪些东西;第三小节给了一段测试代码,其实是让大家自己试试有没有下载成功,能不能把demo运行起来。[4] transformers.BERT的实际使用 第三小节给的代码只是demo。本节将介绍BERT的实际使用,很简单,只需要一个函数tokenizer.encode_plus,但是在写代码前,大家需要了解为什么要这样做。[4.1] BERT模型的输入格式BERT要求我们: 1、在句子的句首和句尾添加特殊的符号[CLS]和[SEP] 2、给句子填充 or 截断,使每个句子保持固定的长度 3、用 attention mask 来显示的区分填充的tokens和非填充的tokens。 特殊符号: [SEP] 在每个句子的结尾,需要添加特殊的[SEP]符号。 在以输入为两个句子的任务中(例如:句子 A 中的问题的答案是否可以在句子 B 中找到),该符号为这两个句子的分隔符。 目前为止还不清楚为什么要在单句中加入该符号,但既然这样要求我们就这么做吧。 [CLS] 在分类任务中,我们需要将[CLS]符号插入到每个句子的开头。 这个符号有特殊的意义,BERT 包含 12 个 Transformer 层,每层接受一组 token 的 embeddings 列表作为输入,并产生相同数目的 embeddings 作为输出(当然,它们的值是不同的)。 句长 & 注意力掩码(Attention Mask) 很明显,数据集中句子长度的取值范围很大,BERT该如何处理这个问题呢? BERT有两个限制条件: 1、所有句子必须被填充或截断到固定的长度,句子最大的长度为512个tokens。 2、填充句子要使用[PAD]符号,它在BERT词典中的下标为0。 下图是最大长度为8个tokens的填充说明: Attention Mask是一个只有 0 和 1 组成的数组,标记哪些tokens是填充的,哪些不是的。掩码会告诉 BERT 中的Self-Attention机制不去处理这些填充的符号。[4.2] BERT模型的最终使用方法 首先使用tokenizer.encode_plus对文本编码:from transformers import BertModel, BertTokenizer # 参数文件地址 pretrained_path = 'bert-base-uncased' # 加载bert模型 bert = BertModel.from_pretrained(pretrained_path) # 加载分词器 tokenizer = BertTokenizer.from_pretrained(pretrained_path) # 对英文文本content进行分词 encoded_dict = config.tokenizer.encode_plus( content, # 输入文本 add_special_tokens=True, # 添加 '[CLS]' 和 '[SEP]' max_length=pad_size, # 填充 & 截断长度 pad_to_max_length=True, padding='max_length', truncation='only_first', return_attention_mask=True, # 返回 attn. masks. return_tensors='pt' # 返回 pytorch tensors 格式的数据 ) # 编码后的文本 IDs = torch.squeeze(encoded_dict['input_ids'],0) # 文本的 attention mask MASKs = torch.squeeze(encoded_dict['attention_mask'],0) 函数tokenizer.encode_plus包含以下步骤: · 将句子分词为 tokens。 · 在两端添加特殊符号 [CLS] 和[SEP]。 · 将 tokens 映射为下标 IDs。 · 将列表填充或截断为固定的长度。 · 创建 attention masks,将填充的和非填充 tokens 区分开来。 更多参数请参考官方文档:https://huggingface.co/docs/transformers/model_doc/bert。 拿到了编码后的文本、文本的attention mask之后,作为输入放入bert模型里面跑:out = bert(IDs, attention_mask=MASKs)【注】这里演示的是基本使用方法,比较独立。真正应用在实战项目里,还是配着整体代码食用更佳。[5] 进行下一篇实战 【BERT-多标签文本分类实战】之六——数据加载与模型代码
·请参考本系列目录:【BERT-多标签文本分类实战】之一——实战项目总览·下载本实战项目资源:>=点击此处=<[1] 数据集预处理的流程 在拿到数据集之后,我们关心接下来操作的步骤: · 查看数据集的基本数据 · 分析数据集的标签构成 · 数据集拆分成训练集、验证集、测试集 · 处理数据集的文本数据(首先了解bert模型的输入) 为什么要这样做? 对于一个新数据集,我们需要:1)把文本转化成嵌入向量;2)把文本的标签转化成独热数组;3)拆分数据集。除此之外,我们还应该关心数据集平均一个本文有几个标签、最多有几个标签、最少有几个标签,哪些标签出现的比较频繁,这些有助于我们加深对数据集、任务难点的了解。【注】 1、嵌入向量:在使用预训练词向量的时候,首先会构建一个词典,然后把文本里面的每个单词逐个转化成词典里面对应的序号,最后根据序号再去预训练词向量里面找对应单词的d维向量,于是一条文本就变成了pad_size * d的嵌入向量。 2、独热数组:是由0-1构成的数组。假设数据集中有5个标签{a,b,c,d,e},文本的实际标签是{b,c},那么它的独热数组就是[0,1,1,0,0]。[2] 查看数据集的基本数据 数据集Rruters-21578的初始数据是放在xlsx文件中的,如下图。 我们观察到该数据集: · 共有10788条数据,其中第2-3020条是test数据,第3021-10789条是training数据,需要进一步划分; · 标签是以数组转成字符串形式存储:['grain', 'rice']; · 文本以英文形式存储,里面有大小写、特殊字符等,需要我们额外处理一下。[3] 分析数据集的标签构成 首先读取数据集,利用正则表达式把每个文本的标签提取出来,查看一共有多少标签:dataset_path = r'@_数据集_原始/Reuters-21578/reutersNLTK.xlsx' df = pd.read_excel(dataset_path, encoding='utf-8', sep=',') result_single = {} // 统计每个标签出现的次数 result_multi = {} a = [] // 存放每个文本的标签,可重复,一维数组 b = [] // 存放每个文本的标签数量,一维数组 # 提取标签 for ls in df['categories']: b.append(len(re.compile(r"'(.*?)'").findall(ls))) for l in re.compile(r"'(.*?)'").findall(ls): a.append(l) # 计算标签次数 for i in set(a): result_single[i] = a.count(i) # 倒叙排列后输出 label_list = sorted([_ for _ in result_single.items()], key=lambda x: x[1], reverse=True) print(label_list) 输出结果: [('earn', 3964), ('acq', 2369), ('money-fx', 717), ('grain', 582), ('crude', 578), ('trade', 485), ('interest', 478), ('ship', 286), ('wheat', 283), ('corn', 237), ('dlr', 175), ('money-supply', 174), ('oilseed', 171), ('sugar', 162), ('coffee', 139), ('gnp', 136), ('gold', 124), ('veg-oil', 124), ('soybean', 111), ('nat-gas', 105), ('bop', 105), ('livestock', 99), ('cpi', 97), ('cocoa', 73), ('reserves', 73), ('carcass', 68), ('jobs', 67), ('copper', 65), ('cotton', 59), ('yen', 59), ('rice', 59), ('alum', 58), ('gas', 54), ('iron-steel', 54), ('ipi', 53), ('barley', 51), ('meal-feed', 49), ('rubber', 49), ('palm-oil', 40), ('sorghum', 34), ('zinc', 34), ('pet-chem', 32), ('tin', 30), ('silver', 29), ('wpi', 29), ('lead', 29), ('rapeseed', 27), ('strategic-metal', 27), ('orange', 27), ('soy-meal', 26), ('soy-oil', 25), ('retail', 25), ('fuel', 23), ('hog', 22), ('housing', 20), ('heat', 19), ('sunseed', 16), ('lumber', 16), ('income', 16), ('lei', 15), ('oat', 14), ('dmk', 14), ('tea', 13), ('platinum', 12), ('groundnut', 9), ('nickel', 9), ('rape-oil', 8), ('l-cattle', 8), ('coconut-oil', 7), ('sun-oil', 7), ('instal-debt', 6), ('potato', 6), ('propane', 6), ('naphtha', 6), ('coconut', 6), ('jet', 5), ('nzdlr', 4), ('cpu', 4), ('palladium', 3), ('nkr', 3), ('dfl', 3), ('copra-cake', 3), ('cotton-oil', 3), ('palmkernel', 3), ('rand', 3), ('lin-oil', 2), ('castor-oil', 2), ('sun-meal', 2), ('groundnut-oil', 2), ('rye', 2)] 可以看到,10788条文本数据中,标签为earn的文本有3964个。接下来我们查看标签个数、文本标签数目:label_key = [word_count[0] for idx, word_count in enumerate(label_list)] label_val = [word_count[1] for idx, word_count in enumerate(label_list)] print("标签个数:", len(label_key)) print("文本标签数目:", set(b)) print("每个文本平均有标签:", sum(label_val) / len(df['categories'])) 输出结果: 标签个数: 90 文本标签数目: {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 15} 每个文本平均有标签: 1.235446792732666 可以看到,每个文本平均有1.2个标签,相对其他数据集来说,是极低的。数据集中文本标签个数为1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 15的都有,我们详细查看它们的分布:k = {} # 计算共现标签 for i in set(b): k[i] = b.count(i) print(k) 输出结果:{1: 9160, 2: 1173, 3: 255, 4: 91, 5: 52, 6: 27, 7: 9, 8: 7, 9: 5, 10: 3, 11: 2, 12: 1, 14: 2, 15: 1} 有9160条文本只有1个标签,有1173条文本是2个标签,再往后就很稀疏了。【注】如何提高模型对这些稀疏数据的预测性能,这也是多标签文本分类中的研究难题。[4] 数据集拆分成训练集、验证集、测试集 我们把数据集中3019条test,分为1500条验证文本和1519条测试文本。在拆分过程中记得要打乱顺序:import random import pandas as pd dataset_path =r'@_数据集_原始/Reuters-21578/reutersNLTK.xlsx' df = pd.read_excel(dataset_path, encoding='utf-8', sep=',') """ 多标签数据集划分 Reuter-21578 label_name : 标签列名 content_name : 内容列名 export_name : 导出的文件名 proportion : 训练-验证-测试比例, 数组, 加和为10, eg: [7,1.5,1.5] """ def divideDataSet(label_name, content_name, export_name, proportion): contents = df[content_name] labels = df[label_name] ids = df['ids'] train, val, test = [], [], [] for i, id in enumerate(ids): if id[:3] == 'tra': train.append({'label': labels[i], 'content': contents[i]}) else: val.append({'label': labels[i], 'content': contents[i]}) random.shuffle(train) random.shuffle(val) test = val[:1500] val = val[1500:] print(len(train), len(val), len(test)) random.shuffle(train) random.shuffle(val) random.shuffle(test) train.insert(0, {'label': 'label', 'content': 'content'}) val.insert(0, {'label': 'label', 'content': 'content'}) test.insert(0, {'label': 'label', 'content': 'content'}) with open(export_name + r'train.csv', 'a', newline='', encoding='utf-8') as f: xieru = csv.DictWriter(f, ['label', 'content'], delimiter=',') xieru.writerows(train) # writerows方法是一下子写入多行内容 with open(export_name + r'dev.csv', 'a', newline='', encoding='utf-8') as f: xieru = csv.DictWriter(f, ['label', 'content'], delimiter=',') xieru.writerows(val) # writerows方法是一下子写入多行内容 with open(export_name + r'test.csv', 'a', newline='', encoding='utf-8') as f: xieru = csv.DictWriter(f, ['label', 'content'], delimiter=',') xieru.writerows(test) # writerows方法是一下子写入多行内容 divideDataSet('categories', 'text', root_path + r'@_数据集_已处理/Reuters-21578/data/', []) 拆分出来3个csv文件。[5] 处理数据集的文本数据 在处理数据集的文本数据前,有必要了解一下使用预处理词向量的模型,是如何处理文本数据的。请参考博客:【英文文本分类实战】之三——数据清洗。 由于bert模型的强大,我们甚至可以不作任何处理,就能直接把文本作为输入放到bert模型中,因为bert模型自带词典(很强大)。我倾向于不作处理,所以这里不再叙述。但其实如果处理的话,也不过是转化一下大小写、过滤特殊字符、过滤缩写,这些在【英文文本分类实战】之三——数据清洗都有详细介绍。[6] 进行下一篇实战 【BERT-多标签文本分类实战】之五——BERT模型库的挑选与Transformers
·请参考本系列目录:【BERT-多标签文本分类实战】之一——实战项目总览·下载本实战项目资源:>=点击此处=<【注】本篇将首先介绍多标签文本分类中几个小方向,然后介绍这几个小方向对应的常用数据集。最后介绍如何自己寻找和下载数据集。[1] 多标签文本分类的若干小方向 目前学术上,多标签文本分类任务大概可以分为3个小方向: · 普通多标签文本分类 · 层级多标签文本分类 · 极端多标签文本分类 不同的小方向,数据集的特点比较不一样,任务的难点也不同。[2] 普通多标签文本分类 普通多标签文本分类就是指我们平常提到的多标签文本分类,英文称作multi-label text classification,MTC。 MTC任务的数据集特点是:数据集中的每条文本,都对应着1-N个标签,在某些特殊数据集中有的文本甚至没有标签。数据集中的总类别标签往往不大,一般在几千以内,一些常用的数据集总标签数在几百、几十以内。而且我们说,MTC任务的数据集中的标签是平面的,标签之间没有归属关系、没有层次结构,是一维展开的。 MTC任务的主要目标很朴素,就是尽量提高模型最后分类出来的各项评价指标,越理想越好。 常用的MTC数据集有: Ren-CECps1.0: 一个多标签的中文情感语料库。它包含了37678 个中文博客的句子和 11 种情感标签,其中每句话被赋予一种或多种情感。 Reuters-21578:是路透社金融新闻服务进行分类的常用数据集,其中包含了 7769 个训练文本和 3019 个测试文本,其中包含多个标签和单个标签。 AAPD:从网络上收集了 55840 篇论文的摘要和相应学科类别,一篇学术论文属于一个或者多个学科,总共由 54个学科组成,目的是根据给定的摘要来预测学术论文相对应的学科。 RCV1-V2:共有 804414篇新闻,每篇新闻故事分配有多个主题,共有 103 个主题。 EUR-Lex:由欧盟法律组成的,里面包含了许多不同类型的文件,包括条约、立法、判例法和立法提案,共有 19314 个文档,3956 个分类。 AmazonCat-13K:该数据集是来自于亚马逊,其中包括评论(评分,文字,帮助性投票),产品元数据(描述,类别信息,价格,品牌和图像特征)和链接(也可以查看/购买的图表),在做多标签文本分类得时候主要考虑的是类别信息。 Amazon-670K:亚马逊商品的评论、产品的数据,和 AmazonCat-13K 的数据有类似之处,只是规模和商品不一样。 Amazon-3M:亚马逊,包含的是产品的信息,链接以及产品的评论。以上8个常用数据集的介绍信息来自论文《多标签文本分类研究进展 - 郝超》本次系列实战项目,采用的数据集就是Reuters-21578。[3] 层次多标签文本分类 层次多标签文本分类是对文本标签具有层次化结构的数据集进行分类,英文称作Hierarchical Multi-label Text Classification, HMTC。 HMTC任务特点是:标签之间具有层次结构,其中,一个标签可以被特殊化为子类然后被一个父类所包含。层次多标签可以采用树(Tree)或者有向无环图(DAG)进行表示。其中对于Tree结构来说,一个标签节点只有一个父节点;而对于DAG结构来说,一个标签节点可以有多个父节点。对于一个给定的样本,同时将一个或多个类标签指定给该样本,并且这些类标签以层次结构的形式存储,这就是层次多标签分类问题。 常用的HMTC数据集有: BlurbGenreCollection(BGC):是作者收集的由书籍介绍以及层次结构的写作题材组成,共有91892 个文本,四个层级,146 个类别,四个层级分别有 7,46,77,16 个类别。 WOS-11967(Web of Science):由 Web of Science 发表的论文的摘要组成,共有11967 个文本,两个层级,40 个类别,两个层级分别有 7,33个类别。 WIPO-alpha2:共有四个层级,5229 个类别,四个层级分别有 8,114,451,4656 个类别。 Enron:是一个邮件的语料数据集,共有三个层级,56 个类别,三个层级分别有 3,40,13 个类别. Reuters:是由路透社提供的人工新闻分类数据集,有超过 800000 条的数据,共有三个层级,101个类别,三个层级分别有 4,55,42 个类别。以上5个常用数据集的介绍信息来自论文《层次多标签文本分类方法 - 赵海燕》[4] 极端多标签文本分类 极端多标签文本分类寻求从一个极端大的标签集合中为给定的文本输入找到相关的标签,英文称作Extreme Multi-label Text Classification, XMTC,其实也可以叫大规模多标签文本分类(Large Scale Multi-label Text Classification, LMTC),一样的意思。 XMTC任务的特点是:标签的数量有成千上百万,特征空间也非常大,严重的样本稀疏性,巨大的内存消耗、显存消耗,成千上百万的标签往往还伴随着层次化结构。标签存在长尾分布,绝大部分标签仅仅有少量样本关联。 XMTC任务的难点有很多。如何解决数据稀疏?如何使模型运行更高效?如何解决样本少的问题?去哪搞服务器跑代码,直接劝退了好吧![5] 总结 数据集越大,越吃设备,所以建议大家还是从较为简单的普通多标签文本分类任务着手入门。其中,数据集最好选择一些不是太大的,例如:Reuters-21578、AAPD、RCV1-V2、EUR-Lex。[6] 进行下一篇实战 【BERT-多标签文本分类实战】之四——数据集预处理
[1] 结论 Java中没有无符号数据类型(它就是这样设计的,我没查到为什么),byte,short,int和long都是有符号数据类型。当我们逻辑上说一个数是无符号类型的话,往往代表我们说它的二进制符号位应该是要纳入计算的,而Java中把这个不该纳入计算的位置当成符号位,所以和我们现象中的数据不一致。特别注意:在Java中,负数以补码存储。 解决办法就是提升类型,把符号位包裹在可计算位中:int toUnsignedInt(byte x) long toUnsignedLong(byte x)[2] 举例说明无符号和有符号的区别 如上图,8位负数-42的原码、反码、补码如图所示。假设我们在逻辑上认为Java中的byte类型的-42是一个无符号类型数字,那么它的实际数字应该是214,即它的补码的机器码大小。 在Java中,由于负数以补码方式存储,所以负数提升为无符号数字时,是把它的补码的符号位纳入计算,而不是原码或者反码。[3] 口算出无符号数 如果想直接口算,可以使用:负数时: 无符号数 = 2^n + 负数 正数时: 无符号数 = 正数 8位负数-42的无符号数 = 2^8 + (-42) = 214。
出错描述 使用sklearn.metrics.precision_score计算精确率时,出现报错:UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 in samples with no predicted labels.问题原理分析 精确率计算的是所有样本的平均精确率。而对于每个样本来说,精确率就是预测正确的标签数在整个预测为正确的标签数中的占比。其计算公式为: 例如对于某个样本来说,其真实标签为[0, 1, 0, 1],预测标签为[0, 0, 0, 0]。那么该样本对应的精确率就应该为:(0 + 1 + 0 + 0) / (0 + 0 + 0 + 0),这时就会报错。情况一 假设有数据:样本数batch_size = 5,标签数label_num = 4。y_true为真实标签,y_pred为预测标签值。y_true = np.array([[0, 1, 0, 1], [0, 1, 1, 0], [0, 0, 1, 0], [1, 1, 1, 0], [1, 0, 1, 1]]) y_pred = np.array([[0, 1, 1, 0], [0, 1, 1, 0], [0, 0, 1, 0], [0, 1, 1, 0], [0, 1, 0, 1]]) 对照上面给的数据y_true、y_pred。那么该样本对应的准确率就应该为:情况二 假设数把y_pred的某一行改为全0,数据如下。y_true = np.array([[0, 1, 0, 1], [0, 1, 1, 0], [0, 0, 1, 0], [1, 1, 1, 0], [1, 0, 1, 1]]) y_pred = np.array([[0, 1, 1, 0], [0, 1, 1, 0], [0, 0, 1, 0], [0, 1, 1, 0], [0, 0, 0, 0]]) 对照上面给的数据y_true、y_pred。那么该样本对应的准确率就应该为:情况三 假设数把y_pred改为全0,数据如下。y_true = np.array([[0, 1, 0, 1], [0, 1, 1, 0], [0, 0, 1, 0], [1, 1, 1, 0], [1, 0, 1, 1]]) y_pred = np.array([[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]) 对照上面给的数据y_true、y_pred。那么该样本对应的准确率就应该为:【注】所以如果有除数为0,sklearn中会默认把数值置为0来计算。解决办法 直接忽略警告即可。import warnings warnings.filterwarnings("ignore")【注】一般一个batch_size都会在32-128,所以有个别样本的精确率为0,最后取平均也还能接收,直接忽略警告就行。
[1] 总览 6个基本评价指标如下思维导图:[2] 介绍 假设有数据:样本数batch_size = 5,标签数label_num = 4。y_true为真实标签,y_pred为预测标签值。y_true = np.array([[0, 1, 0, 1], [0, 1, 1, 0], [0, 0, 1, 0], [1, 1, 1, 0], [1, 0, 1, 1]]) y_pred = np.array([[0, 1, 1, 0], [0, 1, 1, 0], [0, 0, 1, 0], [0, 1, 1, 0], [0, 1, 0, 1]]) [2.1] 子集准确率(Subset Accuracy) 对于每一个样本来说,只有预测值与真实值完全相同的情况下才算预测正确,也就是说只要有一个类别的预测结果有差异都算没有预测正确。因此,其计算公式为: 对照上面给的数据y_true、y_pred。那么只有第2个和第3个样本才算预测正确。在sklearn中,可以直接通过sklearn.metrics模块中的accuracy_score方法来完成计算[3],代码实现:from sklearn.metrics import accuracy_score print(accuracy_score(y_true,y_pred)) # 0.4 print(accuracy_score(y_true,y_pred,normalize=False)) # 2 【注】accuracy_score有参数normalize。normalize = False时:返回完全正确的样本数,normalize = True时:返回完全正确的样本数的占比。[2.2] 准确率(Accuracy) 准确率计算的是所有样本的平均准确率。而对于每个样本来说,准确率就是预测正确的标签数在整个预测为正确或真实为正确标签数中的占比。其计算公式为: 例如对于某个样本来说,其真实标签为[0, 1, 0, 1],预测标签为[0, 1, 1, 0]。那么该样本对应的准确率就应该为:(0 + 1 + 0 + 0) / (0 + 1 + 1 + 1)= 0.33。 对照上面给的数据y_true、y_pred。那么该样本对应的准确率就应该为: 在sklearn中,acc只有子集准确率,所以此处我们要自己实现。代码实现:def Accuracy(y_true, y_pred): count = 0 for i in range(y_true.shape[0]): p = sum(np.logical_and(y_true[i], y_pred[i])) q = sum(np.logical_or(y_true[i], y_pred[i])) count += p / q return count / y_true.shape[0] print(Accuracy(y_true, y_pred)) # 0.65 [2.3] 精确率(Precision) 精确率计算的是所有样本的平均精确率。而对于每个样本来说,精确率就是预测正确的标签数在整个预测为正确的标签数中的占比。其计算公式为: 例如对于某个样本来说,其真实标签为[0, 1, 0, 1],预测标签为[0, 1, 1, 0]。那么该样本对应的精确率就应该为:(0 + 1 + 0 + 0) / (1 + 1)= 0.5。 对照上面给的数据y_true、y_pred。那么该样本对应的准确率就应该为: 代码实现:from sklearn.metrics import precision_score print(precision_score(y_true=y_true, y_pred=y_pred, average='samples'))# 0.8[2.4] 召回率(Recall) 召回率其实计算的是所有样本的平均召回率。而对于每个样本来说,召回率就是预测正确的标签数在整个正确的标签数中的占比。其计算公式为: 例如对于某个样本来说,其真实标签为[0, 1, 0, 1],预测标签为[0, 1, 1, 0]。那么该样本对应的精确率就应该为:(0 + 1 + 0 + 0) / (1 + 1)= 0.5。 对照上面给的数据y_true、y_pred。那么该样本对应的准确率就应该为: 代码实现:from sklearn.metrics import recall_score print(recall_score(y_true=y_true, y_pred=y_pred, average='samples'))# 0.7[2.5] F1 其计算公式为: 例如对于某个样本来说,其真实标签为[0, 1, 0, 1],预测标签为[0, 1, 1, 0]。那么该样本对应的精确率就应该为:2 * (0 + 1 + 0 + 0) / ((1 + 1)+(1 + 1))= 0.5。 对照上面给的数据y_true、y_pred。那么该样本对应的准确率就应该为: 代码实现:from sklearn.metrics import f1_score print(f1_score(y_true,y_pred,average='samples'))# 0.74[2.6] 汉明损失(Hamming Loss) Hamming Loss衡量的是所有样本中,预测错的标签数在整个标签标签数中的占比。所以对于Hamming Loss损失来说,其值越小表示模型的表现结果越好。 对照上面给的数据y_true、y_pred。那么该样本对应的准确率就应该为: 代码实现:from sklearn.metrics import hamming_loss print(hamming_loss(y_true, y_pred))# 0.3
错误写法// wxml页面 <view class="card_box" bindtap="toPage('12')"> </view>// js文件 toPage(name){ console.log(name) } 报错:正确写法 bindtap在传参时需要用到data-xxx来进行传递参数,正确的形式应当为:// wxml页面 <view class="card_box" bindtap="toPage" data-name="12"></view>// js文件 toPage(e){ console.log(e.currentTarget.dataset.name) }
使用示例 wxml<mp-uploader files="{{files}}" max-count="{{maximgs}}" max-size="{{10 * 1024 * 1024}}" title="图片上传" tips="最多上传三张图片" size-type="{{sizeType}}" sourceType="{{sourceType}}" delete="{{true}}" select="{{selectFile}}" upload="{{uplaodFile}}" binddelete="delimg" bindfail="uploadError" bindsuccess="uploadSuccess" ></mp-uploader> json{ "usingComponents": { "mp-toptips": "weui-miniprogram/toptips/toptips", "mp-form": "weui-miniprogram/form/form", "mp-uploader": "weui-miniprogram/uploader/uploader", "mp-checkbox": "weui-miniprogram/checkbox/checkbox", "mp-checkbox-group": "weui-miniprogram/checkbox-group/checkbox-group", "mp-cells": "weui-miniprogram/cells/cells", "mp-cell": "weui-miniprogram/cell/cell" } } js// pages/default/default.js const app = getApp() Page({ data: { //mp-uploader maximgs:5,//最大上传数 files: [], //上传组件绑定的文件urls sizeType: ['compressed'], //压缩上传,可以是['original', 'compressed'] sourceType: ['album', 'camera'], //相册,或拍照 }, onLoad: function (options) { this.setData({ //通过bind(this)将函数绑定到this上,以后函数内的this就是指全局页面 //setdata以后,这两个函数就可以传递给mp-uploader了 selectFile: this.selectFile.bind(this), uplaodFile: this.uplaodFile.bind(this) }) }, //mpuploader选择图片时的过滤函数,返回true表示图片有效 selectFile(files) { wx.showLoading({ title: '', }) // 如果有大文件可以压缩一下 // 返回false可以阻止本次文件上传 }, uplaodFile(files) { // 图片上传的函数,必须返回Promise //Promise的callback里面必须resolve({urls})表示成功,否则表示失败 return new Promise((resolve, reject) => { const tempFilePaths = files.tempFilePaths; const that = this; let finished = {url:[]} //本次上次成功的URL存入这个变量,被success方法的e.detail承接 for (var i = 0; i < tempFilePaths.length; i++) { let filePath = tempFilePaths[i] //原名 let cloudPath = 'qyzj' + new Date().getTime() + '-' + i + filePath.match(/\.[^.]+?$/)[0] //云存储文件名 wx.cloud.uploadFile({ filePath, cloudPath, //成功 success: function (res) { if (res.statusCode != 200 && res.statusCode != 204 && res.statusCode != 205) reject('error')// 可能会有好几个200+的返回码,表示成功 finished.url.push({url:res.fileID}) //成功一个存一个到本次上传成功列表 //如果本次上传的文件都完成 或全局已经存满3张,resolve退出 if (finished.urls.length === tempFilePaths.length || that.data.files.length +finished.urls.length == this.data.maximgs) resolve(finished) }, //失败 fail: function (err) { console.log(err) } }) } }) }, uploadError(e) { console.log('upload error', e.detail) wx.hideLoading() this.setData({ error: "上传失败,可能有些照片过大" }) }, uploadSuccess(e) { console.log('upload success', e.detail) this.data.files=this.data.files.concat(e.detail.url) this.setData({files:this.data.files}) wx.hideLoading() }, //删除图片 detail为{index, item},index表示删除的图片的下标,item为图片对象。 delimg(e) { this.data.files.splice(this.data.files.findIndex(item => item == e.detail.item), 1) } }) 关于weui weui是微信官方提供的一款小程序插件,是一套基于样式库weui-wxss开发的小程序扩展组件库,同微信原生视觉体验一致的UI组件库,由微信官方设计团队和小程序团队设计。官方文档:https://wechat-miniprogram.github.io/weui/docs/。 图片上传组件mp-uploader的属性:属性类型默认值必填说明ext-classstring否添加在组件内部结构的class,可用于修改组件内部的样式titlestring图片上传否组件标题tipsstring否组件的提示deleteboolean是是否显示删除按钮size-typearray是和chooseImage的sizeType参数一样source-typearray是和chooseImage的sourceType参数一样max-sizenumber5 * 1024 * 1024是图片上传的最大文件限制,默认是5Mmax-countnumber1否图片上传的个数限制filesarray否当前的图片列表selectfunction否选择图片时的过滤函数,返回true表示图片有效uploadfunction否图片上传的函数,返回Promise,Promise的callback里面必须resolve({urls})表示成功,否则表示失败bindcanceleventhandler否取消图片选择的事件,detail为{}bindsuccesseventhandler否图片上传成功的事件,detail为{urls},urls为upload函数上传成功返回的urls参数bindfaileventhandler否图片上传失败的事件,detail为{type, errMsg},type为1表示图片超过大小限制,type为2表示选择图片失败,type为3表示图片上传失败。binddeleteeventhandler否删除图片触发的事件,detail为{index, item},index表示删除的图片的下标,item为图片对象。
关于微信小程序原生的表单验证 微信官方是没有提供的,需要我们自己来定义。比较好用的一款第三方表单验证插件是WxValidate.js。 下载地址:https://github.com/wux-weapp/wx-extend/tree/master/src/assets/plugins/wx-validate使用步骤 - 1. 引入WxValidate.js文件 把放入WxValidate.js文件微信小程序utils目录下。 在需要进行表单验证的页面引入:import WxValidate from '../../../utils/WxValidate'使用步骤 - 2. 配置验证规则 首先要在小程序的data属性里面配置好表单的名字: wxml页面的input框里面的value值一定要和js里面的声明的对应上,input的name值也是一样。<form bindsubmit="formSubmit"> <!-- 表单内容 --> <input name="nickName" value="{{form.nickName}}"/> <input name="userName" value="{{form.userName}}"/> <input name="phonenumber" value="{{form.phonenumber}}"/> <input name="password" value="{{form.password}}"/> <input name="remark" value="{{form.remark}}"/> <button form-type="submit">添加用户</button> </form> 然后定义校验规则:initValidate() { const rules = { nickName: { required: true }, userName: { required: true, rangelength: [2,20] }, password: { required: true, rangelength: [5,20] }, phonenumber : { tel: true }, deptId : { required: true } } const messages = { nickName: { required: '单位名称不能为空' }, userName: { required: '账号不能为空', rangelength : '账号长度在2到20之间' }, password: { required: '密码不能为空', rangelength : '密码长度在5到20之间' }, deptId : { required: '所属辖区不能为空' } } this.WxValidate = new WxValidate(rules, messages) }, 然后在onLoad函数中初始化校验规则:onLoad(options) { this.initValidate(); }, 最后写提交表单函数: // 提交表单 formSubmit: function (e) { console.log('表单数据:', e.detail.value) const params = e.detail.value; //校验表单 if (!this.WxValidate.checkForm(params)) { const error = this.WxValidate.errorList[0]; wx.showModal({ content: error, showCancel: false }) return false; } else { // 执行request请求 } }, 建议多看看WxValidate.js源码,写的很好/** * 表单验证 * * @param {Object} rules 验证字段的规则 * @param {Object} messages 验证字段的提示信息 * */ class WxValidate { constructor(rules = {}, messages = {}) { Object.assign(this, { data: {}, rules, messages, }) this.__init() } /** * __init */ __init() { this.__initMethods() this.__initDefaults() this.__initData() } /** * 初始化数据 */ __initData() { this.form = {} this.errorList = [] } /** * 初始化默认提示信息 */ __initDefaults() { this.defaults = { messages: { required: '这是必填字段。', email: '请输入有效的电子邮件地址。', tel: '请输入11位的手机号码。', url: '请输入有效的网址。', date: '请输入有效的日期。', dateISO: '请输入有效的日期(ISO),例如:2009-06-23,1998/01/22。', number: '请输入有效的数字。', digits: '只能输入数字。', idcard: '请输入18位的有效身份证。', equalTo: this.formatTpl('输入值必须和 {0} 相同。'), contains: this.formatTpl('输入值必须包含 {0}。'), minlength: this.formatTpl('最少要输入 {0} 个字符。'), maxlength: this.formatTpl('最多可以输入 {0} 个字符。'), rangelength: this.formatTpl('请输入长度在 {0} 到 {1} 之间的字符。'), min: this.formatTpl('请输入不小于 {0} 的数值。'), max: this.formatTpl('请输入不大于 {0} 的数值。'), range: this.formatTpl('请输入范围在 {0} 到 {1} 之间的数值。'), } } } /** * 初始化默认验证方法 */ __initMethods() { const that = this that.methods = { /** * 验证必填元素 */ required(value, param) { if (!that.depend(param)) { return 'dependency-mismatch' } else if (typeof value === 'number') { value = value.toString() } else if (typeof value === 'boolean') { return !0 } return value.length > 0 }, /** * 验证电子邮箱格式 */ email(value) { return that.optional(value) || /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/.test(value) }, /** * 验证手机格式 */ tel(value) { return that.optional(value) || /^1[34578]\d{9}$/.test(value) }, /** * 验证URL格式 */ url(value) { return that.optional(value) || /^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[/?#]\S*)?$/i.test(value) }, /** * 验证日期格式 */ date(value) { return that.optional(value) || !/Invalid|NaN/.test(new Date(value).toString()) }, /** * 验证ISO类型的日期格式 */ dateISO(value) { return that.optional(value) || /^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/.test(value) }, /** * 验证十进制数字 */ number(value) { return that.optional(value) || /^(?:-?\d+|-?\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/.test(value) }, /** * 验证整数 */ digits(value) { return that.optional(value) || /^\d+$/.test(value) }, /** * 验证身份证号码 */ idcard(value) { return that.optional(value) || /^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}([0-9]|X)$/.test(value) }, /** * 验证两个输入框的内容是否相同 */ equalTo(value, param) { return that.optional(value) || value === that.data[param] }, /** * 验证是否包含某个值 */ contains(value, param) { return that.optional(value) || value.indexOf(param) >= 0 }, /** * 验证最小长度 */ minlength(value, param) { return that.optional(value) || value.length >= param }, /** * 验证最大长度 */ maxlength(value, param) { return that.optional(value) || value.length <= param }, /** * 验证一个长度范围[min, max] */ rangelength(value, param) { return that.optional(value) || (value.length >= param[0] && value.length <= param[1]) }, /** * 验证最小值 */ min(value, param) { return that.optional(value) || value >= param }, /** * 验证最大值 */ max(value, param) { return that.optional(value) || value <= param }, /** * 验证一个值范围[min, max] */ range(value, param) { return that.optional(value) || (value >= param[0] && value <= param[1]) }, } } /** * 添加自定义验证方法 * @param {String} name 方法名 * @param {Function} method 函数体,接收两个参数(value, param),value表示元素的值,param表示参数 * @param {String} message 提示信息 */ addMethod(name, method, message) { this.methods[name] = method this.defaults.messages[name] = message !== undefined ? message : this.defaults.messages[name] } /** * 判断验证方法是否存在 */ isValidMethod(value) { let methods = [] for (let method in this.methods) { if (method && typeof this.methods[method] === 'function') { methods.push(method) } } return methods.indexOf(value) !== -1 } /** * 格式化提示信息模板 */ formatTpl(source, params) { const that = this if (arguments.length === 1) { return function() { let args = Array.from(arguments) args.unshift(source) return that.formatTpl.apply(this, args) } } if (params === undefined) { return source } if (arguments.length > 2 && params.constructor !== Array) { params = Array.from(arguments).slice(1) } if (params.constructor !== Array) { params = [params] } params.forEach(function(n, i) { source = source.replace(new RegExp("\\{" + i + "\\}", "g"), function() { return n }) }) return source } /** * 判断规则依赖是否存在 */ depend(param) { switch (typeof param) { case 'boolean': param = param break case 'string': param = !!param.length break case 'function': param = param() default: param = !0 } return param } /** * 判断输入值是否为空 */ optional(value) { return !this.methods.required(value) && 'dependency-mismatch' } /** * 获取自定义字段的提示信息 * @param {String} param 字段名 * @param {Object} rule 规则 */ customMessage(param, rule) { const params = this.messages[param] const isObject = typeof params === 'object' if (params && isObject) return params[rule.method] } /** * 获取某个指定字段的提示信息 * @param {String} param 字段名 * @param {Object} rule 规则 */ defaultMessage(param, rule) { let message = this.customMessage(param, rule) || this.defaults.messages[rule.method] let type = typeof message if (type === 'undefined') { message = `Warning: No message defined for ${rule.method}.` } else if (type === 'function') { message = message.call(this, rule.parameters) } return message } /** * 缓存错误信息 * @param {String} param 字段名 * @param {Object} rule 规则 * @param {String} value 元素的值 */ formatTplAndAdd(param, rule, value) { let msg = this.defaultMessage(param, rule) this.errorList.push({ param: param, msg: msg, value: value, }) } /** * 验证某个指定字段的规则 * @param {String} param 字段名 * @param {Object} rules 规则 * @param {Object} data 需要验证的数据对象 */ checkParam(param, rules, data) { // 缓存数据对象 this.data = data // 缓存字段对应的值 const value = data[param] !== null && data[param] !== undefined ? data[param] : '' // 遍历某个指定字段的所有规则,依次验证规则,否则缓存错误信息 for (let method in rules) { // 判断验证方法是否存在 if (this.isValidMethod(method)) { // 缓存规则的属性及值 const rule = { method: method, parameters: rules[method] } // 调用验证方法 const result = this.methods[method](value, rule.parameters) // 若result返回值为dependency-mismatch,则说明该字段的值为空或非必填字段 if (result === 'dependency-mismatch') { continue } this.setValue(param, method, result, value) // 判断是否通过验证,否则缓存错误信息,跳出循环 if (!result) { this.formatTplAndAdd(param, rule, value) break } } } } /** * 设置字段的默认验证值 * @param {String} param 字段名 */ setView(param) { this.form[param] = { $name: param, $valid: true, $invalid: false, $error: {}, $success: {}, $viewValue: ``, } } /** * 设置字段的验证值 * @param {String} param 字段名 * @param {String} method 字段的方法 * @param {Boolean} result 是否通过验证 * @param {String} value 字段的值 */ setValue(param, method, result, value) { const params = this.form[param] params.$valid = result params.$invalid = !result params.$error[method] = !result params.$success[method] = result params.$viewValue = value } /** * 验证所有字段的规则,返回验证是否通过 * @param {Object} data 需要验证数据对象 */ checkForm(data) { this.__initData() for (let param in this.rules) { this.setView(param) this.checkParam(param, this.rules[param], data) } return this.valid() } /** * 返回验证是否通过 */ valid() { return this.size() === 0 } /** * 返回错误信息的个数 */ size() { return this.errorList.length } /** * 返回所有错误信息 */ validationErrors() { return this.errorList } } export default WxValidate
出错原因 我在success回调函数是这样赋值的:var that = this; wx.request({ url: this.data.host + "/system/dept/list", method : "GET", success: function (res) { if(res.data.code == 200){ that.data.deptInfo:res.data.data; } } }) 微信官方说明: Page.prototype.setData():setData函数用于将数据从逻辑层发送到视图层,同时改变对应的 this.data 的值。注意: 直接修改 this.data 无效,无法改变页面的状态,还会造成数据不一致。 单次设置的数据不能超过1024kB,请尽量避免一次设置过多的数据。解决办法 应该这样赋值:var that = this; wx.request({ url: this.data.host + "/system/dept/list", method : "GET", success: function (res) { if(res.data.code == 200){ that.setData({ deptInfo:res.data.data }); } } })
· 本文目录 1、损失函数与反向传播的关系 2、添加系数的损失函数会影响实验结果(实验证明) 3、多任务中损失函数与反向传播的思考 4、学习多任务模型损失函数的思路【注】写论文要优化多任务的损失函数,记录下最近的收获,应该能启发一些朋友。不足之处望指正。[1] 损失函数与反向传播的关系 首先要明确,让损失函数的值最小是一个模型学习的目标,而反向传播只是优化模型时求梯度(求导)的一种手段。 反向传播算法的目的是什么? 就是通过更新神经网络的参数也就是每一层的权值、阈值来对损失函数进行最小化。主要方法求出损失函数对参数的梯度,只不过直接用损失函数求出参数的梯度非常困难,我们选择一层一层求损失函数对参数的梯度,更新完这一层的参数再往后传播。 为什么要提出反向传播算法? 在反向传播算法提出之前人们应该想到了使用SGD学习模型,也想到了一些办法求解网络模型的偏导数,但这些算法求解效率比较低,所以提出反向传播算法来更高效的计算偏导数。(那时的网络模型还比较浅只有2-3层,参数少。估计即便不适用反向传播这种高效的算法也能很好的学习。一旦有人想使用更深的网络自然会遇到这个偏导数无法高效计算的问题,提出反向传播也就势在必行了)[2] 添加系数的损失函数会影响实验结果(实验证明) 我们说到所有参数的梯度,是基于损失函数来求的。所以如果仅仅是给损失函数前加个常数系数,每个参数的梯度都会有常数系数倍的变化,理论上会改变实验结果。 我们采用RCNN模型,在路透社多标签数据集上进行实验。 正常的损失函数如下:# 计算多标签损失 def Loss(outputs, labels): loss_f = nn.BCEWithLogitsLoss() loss = loss_f(outputs, labels.float()) return loss 加了常数系数的损失函数如下:# 计算多标签损失 def Loss(outputs, labels): loss_f = nn.BCEWithLogitsLoss() loss = 0.08*loss_f(outputs, labels.float()) return loss 【注】这里的系数是随便设的,不是1和0就行。除此之外,实验是可复现的,随机种子都已固定,其他参数都相同。 最终我们观察到两次实验结果:Test Acc: 81.13%, Test Pre: 89.63%, Test Rec: 88.61%, Test F1: 88.02%, Test OE: 10.71% # 正常loss Test Acc: 82.87%, Test Pre: 90.77%, Test Rec: 89.63%, Test F1: 89.26%, Test OE: 7.14% # 加了系数loss 两次实验结果的不同,说明损失函数仅仅是变了一点(加个常数系数),就会对实验造成影响。 如何理解这种现象? 给损失函数加系数,在反向传播时,梯度必然会变化,同样的学习率在不同的梯度下,就会有不同的学习结果。 对于单任务模型,把这个系数当作是一个超参数去调参的话,我觉得意义不大,这就变成了我们自己去定义损失函数了。而且我们自己修改的损失函数也不一定有泛化能力。 但是把这种系数放到多任务中去调参,是较为可行的。[3] 多任务中损失函数与反向传播的思考 在多任务中一般是两种方法来定义损失函数: 所以这时提出第三种损失函数,把损失函数前的权重当作是可以学习的参数来学习。[4] 学习多任务模型损失函数的思路 但是这样仍然不够,我们需要从理论推导的角度,去找到一些科学的损失函数学习公式。 对于回归任务和单标签分类任务来说,我们可以使用同方差不确定性来替代简单的权重系数来学习。【注】可以参考博客:多任务学习中损失函数权重的自动调整 写的挺好的。我们也可以使用异方差不确定性来替代简单的权重系数来学习,和同方差大同小异。【注】同方差参考论文:Multi-Task Learning Using Uncertainty to Weigh Losses for Scene Geometry and Semantics异方差参考论文:Uncertainty in multitask learning: joint representations for probabilistic MR-only radiotherapy planning
·阅读摘要: 本文提出针对CV领域的多任务模型,设置一个可以学习损失权重的损失层,可以提高模型精度。·参考文献: [1] Multi-Task Learning Using Uncertainty to Weigh Losses for Scene Geometry and Semantics 个人理解:我们使用传统的多任务时,损失函数一般都是各个任务的损失相加,最多会为每个任务的损失前添加权重系数。但是这样的超参数是很难去调参的,代价大,而且很难去调到一个最好的状态。最好的方式应该是交给深度学习。 论文最重要的部分在损失函数的设置与推导。 这对我们优化自己的多任务学习模型有指导意义。[1] Homoscedastic uncertainty as task-dependent uncertainty (同方差不确定性) 作者的数学模型通过贝叶斯模型建立。作者首先提出贝叶斯建模中存在两类不确定性: · 认知不确定性(Epistemic uncertainty):由于缺少训练数据而引起的不确定性 · 偶然不确定性(Aleatoric uncertainty):由于训练数据无法解释信息而引起的不确定性 而对于偶然不确定性,又分为如下两个子类: · 数据依赖地(Data-dependant)或异方差(Heteroscedastic)不确定性 · 任务依赖地(Task-dependant)或同方差(Homoscedastic)不确定性 多任务中,任务不确定性捕获任务间相关置信度,反应回归或分类任务的内在不确定性。【注】本篇论文的假设,是基于同方差不确定性的。关于同方差不确定性和异方差不确定性的通俗解释,可以参考知乎问题:https://www.zhihu.com/question/278182454/answer/398539763[2] Multi-task likelihoods (多任务似然) 对于分类任务有: 多任务的概率: 例如对于回归任务来说,极大似然估计转化为最小化负对数:各个公式的证明pytorch版代码实现 代码如下: import math import pylab import numpy as np import torch import torch.nn as nn from torch.utils.data import Dataset, DataLoader def gen_data(N): X = np.random.randn(N, 1) w1 = 2. b1 = 8. sigma1 = 1e1 # ground truth Y1 = X.dot(w1) + b1 + sigma1 * np.random.randn(N, 1) w2 = 3 b2 = 3. sigma2 = 1e0 # ground truth Y2 = X.dot(w2) + b2 + sigma2 * np.random.randn(N, 1) return X, Y1, Y2 class TrainData(Dataset): def __init__(self, feature_num, X, Y1, Y2): self.feature_num = feature_num self.X = torch.tensor(X, dtype=torch.float32) self.Y1 = torch.tensor(Y1, dtype=torch.float32) self.Y2 = torch.tensor(Y2, dtype=torch.float32) def __len__(self): return self.feature_num def __getitem__(self, idx): return self.X[idx,:], self.Y1[idx,:], self.Y2[idx,:] class MultiTaskLossWrapper(nn.Module): def __init__(self, task_num, model): super(MultiTaskLossWrapper, self).__init__() self.model = model self.task_num = task_num self.log_vars = nn.Parameter(torch.zeros((task_num))) def forward(self, input, targets): outputs = self.model(input) precision1 = torch.exp(-self.log_vars[0]) loss = torch.sum(precision1 * (targets[0] - outputs[0]) ** 2. + self.log_vars[0], -1) precision2 = torch.exp(-self.log_vars[1]) loss += torch.sum(precision2 * (targets[1] - outputs[1]) ** 2. + self.log_vars[1], -1) loss = torch.mean(loss) return loss, self.log_vars.data.tolist() class MTLModel(torch.nn.Module): def __init__(self, n_hidden, n_output): super(MTLModel, self).__init__() self.net1 = nn.Sequential(nn.Linear(1, n_hidden), nn.ReLU(), nn.Linear(n_hidden, n_output)) self.net2 = nn.Sequential(nn.Linear(1, n_hidden), nn.ReLU(), nn.Linear(n_hidden, n_output)) def forward(self, x): return [self.net1(x), self.net2(x)] np.random.seed(0) feature_num = 100 nb_epoch = 2000 batch_size = 20 hidden_dim = 1024 X, Y1, Y2 = gen_data(feature_num) pylab.figure(figsize=(3, 1.5)) pylab.scatter(X[:, 0], Y1[:, 0]) pylab.scatter(X[:, 0], Y2[:, 0]) pylab.show() train_data = TrainData(feature_num, X, Y1, Y2) train_data_loader = DataLoader(train_data, shuffle=True, batch_size=batch_size) model = MTLModel(hidden_dim, 1) mtl = MultiTaskLossWrapper(2, model) mtl # https://github.com/keras-team/keras/blob/master/keras/optimizers.py # k.epsilon() = keras.backend.epsilon() optimizer = torch.optim.Adam(mtl.parameters(), lr=0.001, eps=1e-07) loss_list = [] for t in range(nb_epoch): cumulative_loss = 0 for X, Y1, Y2 in train_data_loader: loss, log_vars = mtl(X, [Y1, Y2]) optimizer.zero_grad() loss.backward() optimizer.step() cumulative_loss += loss.item() loss_list.append(cumulative_loss/batch_size) pylab.plot(loss_list) pylab.show() print(log_vars) [4.2984442710876465, -0.2037072628736496] # Found standard deviations (ground truth is 10 and 1): print([math.exp(log_var) ** 0.5 for log_var in log_vars]) [8.578183137529612, 0.9031617364804738]
[1] 多层感知机(MLP) 最典型的MLP包括包括三层:输入层、隐层和输出层,MLP神经网络不同层之间是全连接的(全连接的意思就是:上一层的任何一个神经元与下一层的所有神经元都有连接)。 由此可知,神经网络主要有三个基本要素:权重、偏置和激活函数 权重:神经元之间的连接强度由权重表示,权重的大小表示可能性的大小 偏置:偏置的设置是为了正确分类样本,是模型中一个重要的参数,即保证通过输入算出的输出值不能随便激活。 激活函数:起非线性映射的作用,其可将神经元的输出幅度限制在一定范围内,一般限制在(-11)或(01)之间。最常用的激活函数是Sigmoid函数,其可将(-∞,+∞)的数映射到(0~1)的范围内。[2] 编码器-解码器(encoder-decoder) 编码器-解码器一般使用在机器翻译任务中,基于注意力机制的编码器-解码器架构如下: 这里只做简单介绍,了解更多可以查看文末的参考文章列表。 编码器的最终隐藏状态可以传给另一个RNN(解码器)。该RNN的每个输出都是输出序列中的一个单词,并作为RNN下一步的输入。然而,这样的架构需要编码器编码整个输入序列为最终隐藏状态。相反,如果使用注意力模型,解码器不仅接受最终隐藏状态作为输入,还接受编码器处理输入序列的每一步的输出作为输入。编码器可以赋予编码器输出不同的权重,在计算解码器输出序列的每次迭代中使用。 解码器循环层的最终输入为注意力加权的编码器输出和循环单元前一步的预测单词索引。下为这一过程的示意图,其中“Context”(上下文)表示编码器输出张量。为了简化图形,示意图中省略了嵌入层。[3] 编码器-解码器(encoder-decoder) 为了解决由长序列到定长向量转化而造成的信息损失的瓶颈,Attention注意力机制被引入了。Attention机制跟人类翻译文章时候的思路有些类似,即将注意力关注于我们翻译部分对应的上下文。同样的,Attention模型中,当我们翻译当前词语时,我们会寻找源语句中相对应的几个词语,并结合之前的已经翻译的部分作出相应的翻译,如下图所示,当我们翻译“knowledge”时,只需将注意力放在源句中“知识”的部分,当翻译“power”时,只需将注意力集中在"力量“。这样,当我们decoder预测目标翻译的时候就可以看到encoder的所有信息,而不仅局限于原来模型中定长的隐藏向量,并且不会丧失长程的信息。 数学运算可以查看文末的参考文章列表。[4] 残差连接(residual connection) 如上图,上面是来自于resnet模型的skip block的示意图。我们可以使用一个非线性变化函数来描述一个网络的输入输出,即输入为X,输出为F(x),F通常包括了卷积,激活等操作。 这就是residual connection的思想,将输出表述为输入和输入的一个非线性变换的线性叠加,没用新的公式,没有新的理论,只是换了一种新的表达。 残差连接是何的首创吗?当然不是,传统的神经网络中早就有这个概念,文【2】中则明确提出了残差的结构,这是来自于LSTM的控制门的思想。可以看出,当总之,为我们所知道的就是下面的式子叫残差连接,residual connection:参考文章:[1] MLP参考:https://zhuanlan.zhihu.com/p/63184325[2] 编码器-解码器参考:https://zhuanlan.zhihu.com/p/52036405[3] 注意力机制参考:https://zhuanlan.zhihu.com/p/46313756[4] skip connect参考:https://zhuanlan.zhihu.com/p/42833949
[1] 编译Vue项目成dist文件夹 输入打包Vue项目语句:npm run build:prod 打包完成后,shell提示如下: 然后在项目中,会出现一个dist文件夹:[2] 把dist文件夹上传到服务器 我的路径如下:[3] 增加配置服务器的nginx 配置代码如下:server { listen 80; server_name localhost; location / { root /www/wwwroot/blueele/dist; try_files $uri $uri/ /index.html; index index.html index.htm; } location /prod-api/{ proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header REMOTE-HOST $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://localhost:8080/; } location /dev-api/{ proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header REMOTE-HOST $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://localhost:8080/; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } } 代码中,配置了访问前端工程的端口为默认80; 配置根目录/www/wwwroot/blueele/dist;要写自己的路径。 其他地方不用改变。[4] 访问 最后,只需要输入自己的域名或者IP即可访问Vue页面。
[1] nacos简要介绍 Nacos就是注册中心+配置中心,等价于SpringCloud的Eureka+Config+Bus。现在国内许多Spring Cloud项目都使用nacos。[2] nacos下载 官网下载地址:https://github.com/alibaba/nacos/releases. linux系统的主机下载tar.gz的压缩包,window下载zip的压缩包。[3] 本地电脑配置nacos(win系统)[1] 在0.7版本之前,在单机模式时nacos使用嵌入式数据库(Derby)实现数据的存储,不方便观察数据存储的基本情况。0.7版本增加了支持mysql数据源能力,所以只要使用0.7及以上版本的nacos,便可以配置mysql数据库,可视化的查看数据的存储情况了。[2] 使用mysql数据库时,需要使用mysql5.7以上版本。 [3.1] 下载下来的nacos压缩包解压,进入解压目录下的bin文件夹下,修改startup.cmd里面的内容。 改为:set MODE="standalone" [3.2] 双击startup.cmd启动naocs.看到成功提示,代表启动成功。 [3.3] 访问http://localhost:8848/nacos/#/login以登录nacos,账号密码都是nacos。3.1~3.3步使用的是naocs内置的数据库,常规项目都是使用我们自己的mysql数据库。下面介绍如何配置mysql数据库。 [3.4] 如下图,是conf文件夹的内容: [1] application.properties是nacos单机启动配置文件。 [2] application.properties.example单机默认配置文件。 [3] cluster.conf.example是nacos集群默认配置文件。 [4] nacos-logback.xml是日志配置文件。 [5] nacos-mysql.sql是mysql数据库初始化脚本。 [6] schema.sql是Derby 数据库初始化脚本 执行nacos-mysql.sql脚本,执行结果,如下。 [3.5] 修改application.properties配置文件: 红色框中的内容是自己设置的数据库名、用户、密码。然后再参照3.1~3.3步即可。[4] 服务器配置nacos(linux系统)1、这里我使用的是阿里云服务器、宝塔镜像;2、大多数操作可以使用可视化界面进行,部分操作还是推荐使用命令语句。 [4.1] 下载下来的tar.gz压缩包解压,进入解压目录下的bin文件夹下,修改startup.sh里面的内容。文件传服务器大家应该都会。 改为:set MODE="standalone" [4.2] 如下图,是conf文件夹的内容: [1] application.properties是nacos单机启动配置文件。 [2] application.properties.example单机默认配置文件。 [3] cluster.conf.example是nacos集群默认配置文件。 [4] nacos-logback.xml是日志配置文件。 [5] nacos-mysql.sql是mysql数据库初始化脚本。 [6] schema.sql是Derby 数据库初始化脚本 执行nacos-mysql.sql脚本,执行结果,如下。 [4.3] 修改application.properties配置文件: 红色框中的内容是服务器中设置的数据库名、用户、密码。 [4.4] 重点来了!在服务器中开启/关闭nacos! 我们在shell命令中进入nacos的bin文件夹(以我的路径举例):cd /usr/local/install/nacos/bin 然后执行启动nacos命令(注意我们是linux系统,用startup.sh):./startup.sh -m standalone如果想重启nacos,一定要记得先kill进程,不然nacos进程会堆叠,系统会崩:# 查找nacos相关进程 ps -aux | grep ’nacos‘ 我们可以看到进程号是14720,然后输入kill进程的命令:kill -9 进程号【特别】在服务器配置nacos后,不需要在防火墙分配8848端口。
首先排除低级错误:1. 上图中5个位置是否写对了?2. 数据库url是否加了时区?serverTimezone=UTC3. 数据库url的主机写的是localhost、还是IP或者域名,在配置数据库的时候,有没有给数据库加了限制本地访问的权限?4. 账号、密码没错吧?可以去IEDA、navicat测试一下连接。解决方案:数据库没有初试化。我是创建了个空数据库,然后就开启nacos了,一直报错No DataSource set。最后把这个空数据库导入nacos的配置之后,成功运行。
本文摘要· 理论来源:【统计学习方法】第二章 感知机· 技术支持:pandas(读csv)、matplotlib(画图)、numpy、sklearn.linear_model.Perceptron(感知机模型)、随机梯度下降思想· 代码目的:利用手写、sklearn两种感知机模型,对鸢尾花数据集进行二分类作者:CSDN 征途黯然.一、鸢尾花(iris)数据集 Iris 鸢尾花数据集是一个经典数据集,在统计学习和机器学习领域都经常被用作示例。数据集内包含 3 类共 150 条记录,每类各 50 个数据,每条记录都有 4 项特征:花萼长度、花萼宽度、花瓣长度、花瓣宽度,可以通过这4个特征预测鸢尾花卉属于(iris-setosa, iris-versicolour, iris-virginica)中的哪一品种。下载地址:点击此处二、代码描述 1、首先,画出数据集中150个数据的前两个特征的散点分布图,我们观察到品种‘Iris-setosa’与‘Iris-versicolor’之间是线性可分的: 2、然后,我们对以上两个品种共100条数据、2个维度进行二分类,利用我们自己定义的感知机模型,效果如下图: 3、利用sklearn库提供的感知机模型,效果如下图,有一个点没有正确分类:三、python代码(注释详细)import pandas as pd import matplotlib.pyplot as plt import numpy as np from sklearn.linear_model import Perceptron """自定义感知机模型""" # 数据线性可分,二分类数据 # 此处为一元一次线性方程 class Model: def __init__(self): # 创建指定形状的数组,数组元素以 1 来填充 self.w = np.ones(len(data[0]) - 1, dtype=np.float32) self.b = 0 # 初始w/b的值 self.l_rate = 0.1 # self.data = data def sign(self, x, w, b): y = np.dot(x, w) + b # 求w,b的值 # Numpy中dot()函数主要功能有两个:向量点积和矩阵乘法。 # 格式:x.dot(y) 等价于 np.dot(x,y) ———x是m*n 矩阵 ,y是n*m矩阵,则x.dot(y) 得到m*m矩阵 return y # 随机梯度下降法 # 随机梯度下降法(SGD),随机抽取一个误分类点使其梯度下降。根据损失函数的梯度,对w,b进行更新 def fit(self, X_train, y_train): # 将参数拟合 X_train数据集矩阵 y_train特征向量 is_wrong = False # 误分类点的意思就是开始的时候,超平面并没有正确划分,做了错误分类的数据。 while not is_wrong: wrong_count = 0 # 误分为0,就不用循环,得到w,b for d in range(len(X_train)): X = X_train[d] y = y_train[d] if y * self.sign(X, self.w, self.b) <= 0: # 如果某个样本出现分类错误,即位于分离超平面的错误侧,则调整参数,使分离超平面开始移动,直至误分类点被正确分类。 self.w = self.w + self.l_rate * np.dot(y, X) # 调整w和b self.b = self.b + self.l_rate * y wrong_count += 1 if wrong_count == 0: is_wrong = True return 'Perceptron Model!' # 得分 def score(self): pass # 导入数据集 df = pd.read_csv('./iris/Iris.csv', usecols=[1, 2, 3, 4, 5]) # pandas打印表格信息 # print(df.info()) # pandas查看数据集的头5条记录 # print(df.head()) """绘制训练集基本散点图,便于人工分析,观察数据集的线性可分性""" # 表示绘制图形的画板尺寸为8*5 plt.figure(figsize=(8, 5)) # 散点图的x坐标、y坐标、标签 plt.scatter(df[:50]['SepalLengthCm'], df[:50]['SepalWidthCm'], label='Iris-setosa') plt.scatter(df[50:100]['SepalLengthCm'], df[50:100]['SepalWidthCm'], label='Iris-versicolor') plt.scatter(df[100:150]['SepalLengthCm'], df[100:150]['SepalWidthCm'], label='Iris-virginica') plt.xlabel('SepalLengthCm') plt.ylabel('SepalWidthCm') # 添加标题 '鸢尾花萼片的长度与宽度的散点分布' plt.title('Scattered distribution of length and width of iris sepals.') # 显示标签 plt.legend() plt.show() # 取前100条数据中的:前2个特征+标签,便于训练 data = np.array(df.iloc[:100, [0, 1, -1]]) # 数据类型转换,为了后面的数学计算 X, y = data[:, :-1], data[:, -1] y = np.array([1 if i == 'Iris-setosa' else -1 for i in y]) """自定义感知机模型,开始训练""" perceptron = Model() perceptron.fit(X, y) # 最终参数 print(perceptron.w, perceptron.b) # 绘图 x_points = np.linspace(4, 7, 10) y_ = -(perceptron.w[0] * x_points + perceptron.b) / perceptron.w[1] plt.plot(x_points, y_) plt.scatter(df[:50]['SepalLengthCm'], df[:50]['SepalWidthCm'], label='Iris-setosa') plt.scatter(df[50:100]['SepalLengthCm'], df[50:100]['SepalWidthCm'], label='Iris-versicolor') plt.xlabel('SepalLengthCm') plt.ylabel('SepalWidthCm') # 添加标题 '自定义感知机模型训练结果' plt.title('Training results of Custom perceptron model.') plt.legend() plt.show() """sklearn感知机模型,开始训练""" # 使用训练数据进行训练 clf = Perceptron() # 得到训练结果,权重矩阵 clf.fit(X, y) # Weights assigned to the features.输出特征权重矩阵 # print(clf.coef_) # 超平面的截距 Constants in decision function. # print(clf.intercept_) # 对测试集预测 # print(clf.predict([[6.0, 4.0]])) # 对训练集评分 # print(clf.score(X, y)) # 绘图 x_points = np.linspace(4, 7, 10) y_ = -(clf.coef_[0][0] * x_points + clf.intercept_[0]) / clf.coef_[0][1] plt.plot(x_points, y_) plt.scatter(df[:50]['SepalLengthCm'], df[:50]['SepalWidthCm'], label='Iris-setosa') plt.scatter(df[50:100]['SepalLengthCm'], df[50:100]['SepalWidthCm'], label='Iris-versicolor') plt.xlabel('SepalLengthCm') plt.ylabel('SepalWidthCm') # 添加标题 'sklearn感知机模型训练结果' plt.title('Training results of sklearn perceptron model.') plt.legend() plt.show()
本文摘要· 前几年买了阿里云的ECS服务器,当时配置宝塔面板都是找朋友帮我弄的,去年一个项目给某公司做官网,涉及到域名解析的事情,最近开发项目又涉及到部署PHP项目、部署JavaWeb项目的工作。抽空做个运维的博客总结,这些知识对刚接触的新人太不友好了。·本文基于读者已经配置好宝塔面板的前提,讲解如何配置站点、如何做域名的DNS解析(用域名访问网站)作者:CSDN 征途黯然.一、配置域名的流程介绍 1 【服务器】 默认大家用的是阿里云的ECS轻量服务器,镜像用的是宝塔。 2 【域名】 2.1、首先去买一个域名,建议域名和服务器的供应商要一致,对域名没有太大要求的话,一般域名一年¥10-¥100之内完全可以搞定。 2.2、域名一定要备案,不备案没法使用(或者会停掉站点),备案分个人备案、企业备案。我以前做的是企业备案,纯线上,在工信部、阿里这边都得备案,大概是3-4天完成的。我有个朋友自己买域名做个人博客网站备案,前前后后花了20-25天,因为他在工信部备案的时候,那边会寄幕布让他自拍…… 3 【DNS解析】 域名备案完成之后,可以对它进行DNS解析。所谓DNS解析,就是把域名与你的服务器地址相关联(或者其他地址)。假设购买了一个域名:www.example.com,我们就可以对一级域名www.example.com、二级域名***.example.com进行解析,解析到服务器的地址空间(形象地说,就是把域名与我们的服务器相关联)。 4 【宝塔内配置站点】 DNS做好之后,并不代表你通过urlhttp://www.example.com就可以访问服务器资源了。需要在宝塔面板中添加站点,在这一步,主要做的是为域名配置端口号、配置它在服务器里面的文件夹映射。 5 【开通端口号】我们在宝塔中添加了站点之后,宝塔会自动开放指定的端口,但是在阿里的后台,对这个端口还是关闭状态。需要去阿里云后台开通端口,这样站点就可以正常访问了。二、域名DNS解析步骤截图 1、进入阿里云“云解析 DNS”模块,找到需要解析的域名。 2、然后按照自己的情况配置域名到你的服务器地址。 3、前2步弄完之后,如果访问你配置的域名,会发现不能使用,会出现以下画面。 4、这是由于还没有在服务器端配置域名,到宝塔界面的“网站”中添加站点。 5、之后,去宝塔面板的“安全”中,开放域名相应的端口。再去阿里云服务器管理后台的“防火墙”开放相应的端口。 6、最后再去访问域名,就可以正常访问了!之后去对应的根目录上传你的文件就可以了。其实搞懂了,会发现还是比较简单的~
步骤一、安装pyinsatller 打开命令行窗口,输入如下指令pip3 install pyinstalle 等待pyinsatller库安装完成。步骤二、使用pyinstaller打包Python程序 1. 选中你要打包的py文件的上级目录,按住shift并且右键,在弹出的选项中点击"在此处打开命令行窗口"或者“在此处打开PowerShell窗口”。 2. 输入以下指令,开始打包pyinstaller -F -w (-i icofile) filename · filename表示py文件名 · -w 表示隐藏程序运行时的命令行窗口(不加-w会有黑色窗口) · 括号内的为可选参数,-i icofile表示给程序加上图标,图标必须为.ico格式 · icofile表示图标的位置,建议直接放在程序文件夹里面,这样子打包的时候直接写文件名就好 · 输入完成,按回车,就会开始自动打包了,第一次打包过程可能比较缓慢操作演示 1. 在工程里面需要有个主py文件,我命名为main.py。main.py里面的内容如下:# 这里可以import任意库 print("这是输出") 2. 然后我在工程文件夹按"shift"+右键,点"在此处打开PowerShell窗口"。 3. 输入语句,开始打包main.py。pyinstaller -F -w main.py 4. 打包完成,在dist文件夹里面有生成的main.exe。最终生成的文件main.exe,双击即可运行。注意事项!! · 在导入库文件的时候,尽可能的小。用from xxx import xxx。这样在打包的时候,它提取的库文件不会多余,整个打包的项目也会小一点。 · 导入的exe在dist文件夹里面,不能动,它需要用其他文件夹里面的库文件。
当前为2021/3/9的解决方案问题描述为了使用activiti插件,我安装了IDEA的2019.1.4社区版本。发现在把bpmn后缀改为xml之后,右键没有diagrams选项。解决方案(我的)我首先重新安装了IDEA的2019.1.4旗舰版(只有30天试用期)。然后在插件商城里搜索bpm,发现出现了4个插件,都安装上就好了:
样式:样式:<html> <head> </head> <style> body{ text-align: center; } .hcqFont{position:relative;letter-spacing:.07em;font-size:3em;font-weight:normal;margin:0 auto} .hcqFont:before,.hcqFont:after{position:absolute;top:0;left:0;right:0} .hcqStyle1{color:hsl(184,80%,25%);text-shadow:0 0 1px currentColor,/*highlight*/-1px -1px 1px hsl(184,80%,50%),0 -1px 1px hsl(184,80%,55%),1px -1px 1px hsl(184,80%,50%),/*light shadow*/1px 1px 1px hsl(184,80%,10%),0 1px 1px hsl(184,80%,10%),-1px 1px 1px hsl(184,80%,10%),/*outline*/-2px -2px 1px hsl(184,80%,15%),-1px -2px 1px hsl(184,80%,15%),0 -2px 1px hsl(184,80%,15%),1px -2px 1px hsl(184,80%,15%),2px -2px 1px hsl(184,80%,15%),2px -1px 1px hsl(184,80%,15%),2px 0 1px hsl(184,80%,15%),2px 1px 1px hsl(184,80%,15%),-2px 0 1px hsl(184,80%,15%),-2px -1px 1px hsl(184,80%,15%),-2px 1px 1px hsl(184,80%,15%),/*dark shadow*/2px 2px 2px hsl(184,80%,5%),1px 2px 2px hsl(184,80%,5%),0 2px 2px hsl(184,80%,5%),-1px 2px 2px hsl(184,80%,5%),-2px 2px 2px hsl(184,80%,5%)} .hcqStyle2{display:inline-block;font-weight:bold;color:#def;text-shadow:0 0 1px currentColor,-1px -1px 1px #000,0 -1px 1px #000,1px -1px 1px #000,1px 0 1px #000,1px 1px 1px #000,0 1px 1px #000,-1px 1px 1px #000,-1px 0 1px #000;-webkit-filter:url(#diff1);filter:url(#diff1);/*background:#def;padding:0 .2em*/} .hcqStyle3{background: #EEE url(data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAIAAAAmkwkpAAAAHklEQVQImWNkYGBgYGD4//8/A5wF5SBYyAr+//8PAPOCFO0Q2zq7AAAAAElFTkSuQmCC) repeat;text-shadow: 5px -5px black, 4px -4px white;font-weight: bold;-webkit-text-fill-color: transparent;-webkit-background-clip: text} .hcqStyle4{color: transparent;-webkit-text-stroke: 1px red;letter-spacing: 0.04em;} .hcqStyle5{color: transparent;background-color : blue;text-shadow : rgba(255,255,255,0.5) 0 5px 6px, rgba(255,255,255,0.2) 1px 3px 3px;-webkit-background-clip : text;} .hcqStyle6{color: gold;letter-spacing: 0;text-shadow: 0px 1px 0px #999, 0px 2px 0px #888, 0px 3px 0px #777, 0px 4px 0px #666, 0px 5px 0px #555, 0px 6px 0px #444, 0px 7px 0px #333, 0px 8px 7px #001135} .hcqStyle7{font-family:cursive;text-shadow:6px 2px 2px #333;color:deeppink} .text-reflect-base{color: palegreen;-webkit-box-reflect:below 10px;} .text{ width: 300px; height: 200px; position: absolute; left: 50%; margin-left: -150px; background-image: -webkit-linear-gradient(left,blue,#66ffff 10%,#cc00ff 20%,#CC00CC 30%, #CCCCFF 40%, #00FFFF 50%,#CCCCFF 60%,#CC00CC 70%,#CC00FF 80%,#66FFFF 90%,blue 100%); -webkit-text-fill-color: transparent; -webkit-background-clip: text; -webkit-background-size: 200% 100%; -webkit-animation: masked-animation 4s linear infinite; font-size: 35px; } @keyframes masked-animation { 0% { background-position: 0 0; } 100% { background-position: -100% 0; } } </style> <body> <h1 class='hcqFont hcqStyle1'>hcqFont hcqStyle1</h1> <h1 class='hcqFont hcqStyle2'>hcqFont hcqStyle2</h1> <h1 class="hcqStyle3">hcqStyle3</h1> <h1 class="hcqStyle4">hcqStyle4</h1> <h1 class="hcqStyle5">hcqStyle5</h1> <h1 class="hcqStyle6">hcqStyle6</h1> <h1 class="hcqStyle7">hcqStyle7</h1> <h1 class='text-reflect-base'>text-reflect-base</h1> <div class="text"><p>text</p></div> </body> </html>
安装mysql包npm install mysql --savemysql的配置信息// 数据库配置文件 var mysql = require('mysql'); const config = { host : 'localhost', database : 'wish', username : 'root', password : '123' } var connection = mysql.createConnection({ host: config.host, //数据库地址 user: config.username,//用户名 password: config.password,//密码 database: config.database,//数据库名称 timezone:'+08:00' }); module.exports = connection; mysql基本操作mysql的基本操作即为:读、写两个操作。读操作用SELECT关键字查询出结果之后,返回给html页面渲染数据。写操作是html页面的< form>标签提交的数据,写入mysql数据库。查询mysql并渲染数据// 设置‘/’的路由 router.get('/',function getList(req,res){ var sql = 'SELECT * FROM wish'; Connection.query(sql, function(err, result) { if (err) { console.log('[SELECT 错误]:', Constant.DEFAULT_ERROR); } // 为index页面的data变量,赋值为查询结果result // 之后前端会渲染数据 res.render('index',{ data:result }) }) }); mysql插入操作插入操作需要借助< form >标签,提示点击提交之后,会跳转到另外一个页面,还是没有ajax方便。首先在html页面写上< form >标签<form action="add" method="post" id="form"> <input name="name"> <input name="content"> <button type="submit">提交</button> </form> 点击submit按钮之后,提交的路由就是‘/add’,在服务器端获取的request.body中会包含name、content参数值。开始插入router.post('/add',function add(req,res){ if(req.body.name==''&&req.body.content==''){ console.log('不可为空'); } var sql = `INSERT INTO wish VALUES('112','${req.body.name}','${req.body.content}','1','')`; Connection.query(sql, function(err, result) { if (err) { console.log('[SELECT 错误]:', Constant.DEFAULT_ERROR); } //这里理因写个跳转页面 }) }); mysql连接池为什么要采用数据库连接池?此处引用了知乎用户:Drol的回答应用程序和数据库建立连接的过程是这样的:1、首先通过TCP协议的三次握手和数据库服务器建立连接,然后发送数据库用户账号密码,等待数据库验证用户身份。2、 完成用户身份验证后,系统才可以提交SQL语句到数据库执行。3、 好了这个时候假设我们不使用数据库连接池,那么完成一次SQL查询后,我们还要把连接关闭,关闭连接就需要和数据库通信告诉它我们要断开连接了然后再TCP四次挥手最后完成关闭。这个过程中每一次发起SQL查询所经历的TCP建立连接,数据库验证用户身份,数据库用户登出,TCP断开连接消耗的等待时间都是可以避免的,这明显是一种浪费。打个比方,你去网吧去玩游戏,每次去到呢先插网线,然后开机登录游戏,玩了一会儿要去上厕所,你就退出游戏,然后关机拔网线。去完厕所回来就又重新插网线开机登游戏。有没有觉得上面例子中的行为很扯蛋,所以每次SQL查询都创建链接,查询完后又关闭连接这个做法本身就很扯蛋。合理的做法就应该是系统启动的时候就创建数据库连接,然后需要使用SQL查询的时候,就从系统拿出数据库连接对象并提交查询,查询完了就把连接对象还给系统。系统在整个程序运行结束的时候再把数据库连接关闭。考虑到一般数据库应用都是Web多用户并发应用,那么只有一个数据库连接对象肯定不够用,所以系统启动的时候就应该多创建几个数据库连接对象供多个线程使用,这一批数据库连接对象集合在一起就被称之为数据库连接池。数据库连接池就是典型的用空间换时间的思想,系统启动预先创建多个数据库连接对象虽然会占用一定的内存空间,但是可以省去后面每次SQL查询时创建连接和关闭连接消耗的时间。以空间换空间,避免多次TCP/IP的三次握手与四次挥手。node中mysql数据库连接池的写法var mysql = require('mysql'); var pool = mysql.createPool({ host : "hostName", user : "username", password: "password" }); pool.getConnection(function(err, connection){ connection.query( "select * from table1", function(err, rows){ if(err) { throw err; }else{ console.log( rows ); } }); connection.release(); }); mysql事务事务的应用场景主要针对多条SQL语句同时操作,其中某一条语句发生错误,那么数据库应回到之前所有语句还未操作的状态。成为事务的回滚。
序言1:“你喜欢的我也喜欢”,这句话算是推荐系统最精华的提炼了。本篇文章将详细讲述推荐系统的几种算法,并尝试用js来实现它。序言2:由于让数据形象化,我采用了JS的图表框架Chart.js(因为前几天写了几篇Chart.js的笔记)。在阅读本篇文章时,你可能会担心没有掌握Chart.js而看不懂。完全没必要!Chart.js只是我画图展示给大家的工具,大家也可以用Python库来画图。图表一览话不多说,上图:目前仍然不需要看懂这些图,我会在后面一一讲解。试想一个推荐系统的应用场景假设,我们爬取了豆瓣的10位用户对5本图书的打分,json数据如下:dataBase = { "徐凤年":{"雪中悍刀行":0,"挪威的森林":3,"平凡的世界":1,"资治通鉴":3,"情书1946":5}, "裴南苇":{"雪中悍刀行":4,"挪威的森林":0,"平凡的世界":3,"资治通鉴":4,"情书1946":5}, "曹长卿":{"雪中悍刀行":5,"挪威的森林":3,"平凡的世界":0,"资治通鉴":2,"情书1946":5}, "李淳罡":{"雪中悍刀行":0,"挪威的森林":5,"平凡的世界":4,"资治通鉴":5,"情书1946":1}, "王仙芝":{"雪中悍刀行":5,"挪威的森林":5,"平凡的世界":1,"资治通鉴":1,"情书1946":2}, "温华":{"雪中悍刀行":5,"挪威的森林":3,"平凡的世界":5,"资治通鉴":4,"情书1946":1}, "姜泥":{"雪中悍刀行":0,"挪威的森林":3,"平凡的世界":4,"资治通鉴":4,"情书1946":1}, "严池集":{"雪中悍刀行":0,"挪威的森林":5,"平凡的世界":0,"资治通鉴":1,"情书1946":5}, "拓跋菩萨":{"雪中悍刀行":5,"挪威的森林":4,"平凡的世界":1,"资治通鉴":3,"情书1946":2}, "黄三甲":{"雪中悍刀行":5,"挪威的森林":3,"平凡的世界":1,"资治通鉴":4,"情书1946":2}, } 其中,0分代表该用户并未对这本书打分,打分取值为1、2、3、4、5。现在你所在的公司有类似如下的场景需要解决:根据这个数据集,为用户“徐凤年”推荐几本他应当喜欢的书。分析应用场景的需求为了解决上述问题,我们可以把这个需求分成两步解决。首先,找到与用户“徐凤年”相似的用户。然后,根据找到的相似用户,取相似用户评分高的,而“徐凤年”没有看过的推荐给他,这样,他应当会喜欢看。开始开发推荐图书系统常规解决想法上面的数据看起来虽然比较整齐,但是看不出来任何特点,所以我开始尝试画个曲线图试试看,如图1:似乎好看了一点,但是各种线都缠绕在了一起。这只是5本书而已,随着应用场景的扩大,分析几万本书也是很有可能的,到时候几万跟曲线缠绕在一起,根本看不出什么规律。所以我们尝试一些数据分析与挖掘的算法。我发誓,我以前看书的时候也和大家一样,看到公式立马跳过。但是我可以保证接下来的几个公式都非常常见,而且很容易理解,而且大家在高中就都已经学过。现在正是学以致用的时候!曼哈顿距离【原理】最简单的距离计算方式是曼哈顿距离。在二维模型中,每个人都可以用(x, y)的点来表示,这里我用下标来表示不同的人,(x1, y1)表示“徐凤年”,(x2, y2)表示“裴南苇”,那么他们之间的曼哈顿距离就是:在直角坐标系中,就是三角形的两条边长度之和。如果拓展到多维模型,公式就是:最终得出的值越小,代表两者越相似。【实际问题分析】对于“徐凤年”和“裴南苇”,他们对5本书的打分情况如下:雪中悍刀行挪威的森林平凡的世界资治通鉴情书1946徐凤年03135裴南苇40345【注意】这里面值为0的,代表用户还没有打分,我们不应该纳入计算,因为用户如果打分,可能打1-5分,而我们用0代入计算肯定是不妥的。试想一下,网络上假设有2000w首个,而我们每个人最多只能听1w首,而当我们计算时,如果把剩下的1900w首没听过的歌都以0值代入计算,那么得出的值会非常不准确,甚至是毫无关联。所以,得出的曼哈顿距离为:|1-3| + |3-4| + |5-5| = 3【JS算法代码】/** 曼哈顿距离算法**/ let manhattan = (dataChild,dataFather) => { let arr = [];// 用于返回距离的数组 let child = [];// 存放提取出的、被比较的子数据 let father = [];// 存放提取出的所有 /** 开始提取数据 **/ for(key in dataChild) child.push(dataChild[key]) for(key1 in dataFather){ let arr_child = []; for(key2 in dataFather[key1]) arr_child.push(dataFather[key1][key2]) father.push(arr_child) } // console.log(child,father) /**开始计算**/ for(let i=0;i<father.length;i++){ let len = 0; for(let j=0;j<child.length;j++){ if(child[j]!=0&&father[i][j]!=0) len += Math.abs(child[j] - father[i][j]) } arr.push(len); } return arr; } console.log(manhattan(dataBase["徐凤年"],dataBase)); 得出的结果为:[0,3,1,11,7,9,8,4,4,4]我们根据上述数组来作个折线图:很容易可以看出,“曹长卿”是其他9个人里面与“徐凤年”的曼哈顿距离最小的,即他们对5本书的打分情况最相似。欧氏距离【原理】也许你还隐约记得勾股定理。另一种计算距离的方式就是看两点之间的直线距离:最终得出的值越小,代表两者越相似。【实际问题分析】如果拓展到n维模型,同样对于下面的表:雪中悍刀行挪威的森林平凡的世界资治通鉴情书1946徐凤年03135裴南苇40345对于有0值的,我们仍然不纳入计算可以得出,欧氏距离为: Math.sqrt( (1-3)^2 + (3-4)^2 + (5-5)^2 ) = 根号5【JS算法代码】我们来模拟一下欧氏距离的算法:/** 欧式距离算法**/ let euclidean = (dataChild,dataFather) => { let arr = [];// 用于返回距离的数组 let child = [];// 存放提取出的、被比较的子数据 let father = [];// 存放提取出的所有 /** 开始提取数据 **/ for(key in dataChild) child.push(dataChild[key]) for(key1 in dataFather){ let arr_child = []; for(key2 in dataFather[key1]) arr_child.push(dataFather[key1][key2]) father.push(arr_child) } // console.log(child,father) /**开始计算**/ for(let i=0;i<father.length;i++){ let len = 0; for(let j=0;j<child.length;j++){ if(child[j]!=0&&father[i][j]!=0) len += Math.pow((child[j] - father[i][j]),2) } arr.push(Math.sqrt(len)); } return arr; } console.log(euclidean(dataBase["徐凤年"],dataBase)); 得到的输出为:[0, 2.23606797749979, 13: 5.744562646538029, 4.123105625617661, 5.744562646538029 ,5.0990195135927845, 2.8284271247461903, 3.1622776601683795, 3.1622776601683795 ]我们把数据绘制成折线图:可以看出,与“徐凤年”喜欢相似的用户仍然是“曹长卿”。闵可夫斯基距离【原理】我们可以将曼哈顿距离和欧几里得距离归纳成一个公式,这个公式称为闵可夫斯基距离:其中:r = 1 该公式即曼哈顿距离r = 2 该公式即欧几里得距离r = ∞ 极大距离提前预告一下:r值越大,单个维度的差值大小会对整体距离有更大的影响。【JS算法代码】我们来模拟一下闵可夫斯基的算法:/** 闵可夫斯基距离算法**/ let minkowski = (dataChild,dataFather) => { let arr = [];// 用于返回距离的数组 let child = [];// 存放提取出的、被比较的子数据 let father = [];// 存放提取出的所有 /** 开始提取数据 **/ for(key in dataChild) child.push(dataChild[key]) for(key1 in dataFather){ let arr_child = []; for(key2 in dataFather[key1]) arr_child.push(dataFather[key1][key2]) father.push(arr_child) } // console.log(child,father) /**开始计算**/ for(let i=0;i<father.length;i++){ // 计算r-即去掉等于0的组别,剩下的组数 let r = child.length; for(let j=0;j<child.length;j++){ if(child[j]==0||father[i][j]==0) r--; } let len = 0; for(let j=0;j<child.length;j++){ if(child[j]!=0&&father[i][j]!=0) len += Math.pow(Math.abs(child[j] - father[i][j]),r) } arr.push(Math.pow(len,1/r)); } return arr; } console.log(minkowski(dataBase["徐凤年"],dataBase)) 得出的数组为:[0,2.080083823051904,1,4.382849839122776,3.260390438695134,4.759149430918539,4.287747230288912,2.5198420997897464,3.009216698434564,3.009216698434564]我们绘制成折线图:与“徐凤年”的喜好相似用户依然是“曹长卿”!到这里,大家可以在上面的图片一览中,对比图2、图3、图4。看看他们之间的规律。我们会发现,他们的规律相似,也就是我们采用的三种算法最终指向同一种结果!皮尔逊相关系数我们再提出一个问题:如果一个用户A打分标准是:4分为不喜欢,5分为喜欢;用户B的打分标准为:1分为不喜欢,2分适中,3分为最喜欢。如果我们此时再用距离算法计算两者,就会不太恰当。由于每个用户的打分标准不同,会影响推荐系统的准确性。于是我们提出皮尔逊相关系数,公式为:这不就是高中学的回归分析里面 y = ax + b,其中a的公式嘛!皮尔逊相关系数用于衡量两个变量之间的相关性,它的值在-1至1之间,1表示完全吻合,-1表示完全相悖。【JS算法代码】/** 皮尔逊相关系数算法**/ let pearson = (dataChild,dataFather) => { let arr = [];// 用于返回距离的数组 let child = [];// 存放提取出的、被比较的子数据 let father = [];// 存放提取出的所有 /** 开始提取数据 **/ for(key in dataChild) child.push(dataChild[key]) for(key1 in dataFather){ let arr_child = []; for(key2 in dataFather[key1]) arr_child.push(dataFather[key1][key2]) father.push(arr_child) } // console.log(child,father) /**开始计算**/ for(let i=0;i<father.length;i++){ // 计算r-即去掉等于0的组别,剩下的组数 let r = child.length; for(let j=0;j<child.length;j++){ if(child[j]==0||father[i][j]==0) r--; } // 计算x、y平均 let _x=0; let _y = 0; for(let j=0;j<child.length;j++) if(child[j]!=0&&father[i][j]!=0){ _x += child[j]; _y += father[i][j]; } _x /= r; _y /= r; // 计算分子 let fenzi = 0; for(let j=0;j<child.length;j++) if(child[j]!=0&&father[i][j]!=0){ fenzi += (child[j] - _x)*(father[i][j] - _y) } // 计算分母 let fenmu = 0; let fenmux = 0; let fenmuy = 0; for(let j=0;j<child.length;j++) if(child[j]!=0&&father[i][j]!=0){ fenmux += Math.pow((child[j] - _x),2); fenmuy += Math.pow((father[i][j] - _y),2); } fenmu = Math.sqrt(fenmux*fenmuy) arr.push(fenzi/fenmu); } return arr; } console.log(pearson(dataBase["徐凤年"],dataBase)) 把结果绘制成折线图:可以看出来,“裴南苇”与“曹长卿”和“徐凤年”的喜好最吻合。余弦相似度【原理】其中,“·”号表示数量积。“||x||”表示向量x的模,计算公式是:余弦相似度的范围从1到-1,1表示完全匹配,-1表示完全相悖。所以0.935表示匹配度很高。【JS算法代码】/** 余弦相似度算法**/ let cossimilarity = (dataChild,dataFather) => { let arr = [];// 用于返回距离的数组 let child = [];// 存放提取出的、被比较的子数据 let father = [];// 存放提取出的所有 /** 开始提取数据 **/ for(key in dataChild) child.push(dataChild[key]) for(key1 in dataFather){ let arr_child = []; for(key2 in dataFather[key1]) arr_child.push(dataFather[key1][key2]) father.push(arr_child) } // console.log(child,father) /**开始计算**/ for(let i=0;i<father.length;i++){ // 计算xy的和、x2的和、y2的和 let Cxy = 0; let Cxx = 0; let Cyy = 0; for(let j=0;j<child.length;j++) if(child[j]!=0&&father[i][j]!=0){ Cxy += child[j] * father[i][j]; Cxx += Math.pow(child[j],2); Cyy += Math.pow(father[i][j],2); } arr.push(Cxy/(Math.sqrt(Cxx*Cyy))) } return arr; } console.log(cossimilarity(dataBase["徐凤年"],dataBase)) 我们把结果绘制:可以看出来,“曹长卿”的余弦相似度最大,与“徐凤年”最相似。得出推荐图书由以上5种算法,我们得出“曹长卿”是“徐凤年”的喜好相似用户。他们对图书的打分情况如下:雪中悍刀行挪威的森林平凡的世界资治通鉴情书1946徐凤年03135曹长卿53025如上表,我们找一本“曹长卿”评分高,而“徐凤年”没有读过的书,推荐给“徐凤年”,他可能会喜欢。则最终,我们给用户“徐凤年”推荐图书《雪中悍刀行》。应该使用哪种相似度?如果数据存在“分数膨胀”问题,就使用皮尔逊相关系数。如果数据比较“密集”,变量之间基本都存在公有值,且这些距离数据是非常重要的,那就使用欧几里得或曼哈顿距离。如果数据是稀疏的,则使用余弦相似度。这里解释一下“分数膨胀”。形象的说,就是每个用户的评分标准不一样。系统给的标准是1-5依次递增,但是有的人认为最好的应该打4分,最差的打2分;有的人认为差的打4分,好的打5分;他们有自己的标准,而且他们的标准并不符合系统的1-5分的设定。这个时候我们就应该用皮尔逊相关系数。
实验要求实验目的与要求用高级语言编写和调试一个简单的文件系统,模拟文件管理的工作过程,从而对各种文件操作命令的实质内容和执行过程有比较深入的了解。要求设计一个 n个用户的文件系统,每次用户可保存m个文件,用户在一次运行中只能打开一个文件,对文件必须设置保护措施,且至少有Create、delete、open、close、read、write等命令。二、例题:设计一个10个用户的文件系统,每次用户可保存10个文件,一次运行用户可以打开5个文件。程序采用二级文件目录(即设置主目录[MFD])和用户文件目录(UED);另外,为打开文件设置了运行文件目录(AFD)。为了便于实现,对文件的读写作了简化,在执行读写命令时,只需改读写指针,并不进行实际的读写操作算法与框图:因系统小,文件目录的检索使用了简单的线性搜索。文件保护简单使用了三位保护码:允许读写执行、对应位为 1,对应位为0,则表示不允许读写、执行。程序中使用的主要设计结构如下:主文件目录和用户文件目录( MFD、UFD)打开文件目录( AFD)(即运行文件目录)文件系统算法的流程图如下:三、实验题:增加 2~3个文件操作命令,并加以实现(如移动读写指针,改变文件属性,更换文件名,改变文件保护级别)。编一个通过屏幕选择命令的文件管理系统,每屏要为用户提供足够的选择信息,不需要打入冗长的命令。设计一个树型目录结构的文件系统,其根目录为 root,各分支可以是目录,也可以是文件,最后的叶子都是文件。根据学校各级机构,编制一文件系统。实验报告1.实验目的用高级语言编写和调试一个简单的文件系统,模拟文件管理的工作过程,从而对各种文件操作命令的实质内容和执行过程有比较深入的了解。要求设计一个 n个用户的文件系统,每次用户可保存m个文件,用户在一次运行中只能打开一个文件,对文件必须设置保护措施,且至少有Create、delete、open、close、read、write等命令。2.实验内容与要求设计一个10个用户的文件系统,每次用户可保存10个文件,一次运行用户可以打开5个文件。程序采用二级文件目录(即设置主目录[MFD])和用户文件目录(UED);另外,为打开文件设置了运行文件目录(AFD)。为了便于实现,对文件的读写作了简化,在执行读写命令时,只需改读写指针,并不进行实际的读写操作算法与框图:因系统小,文件目录的检索使用了简单的线性搜索。文件保护简单使用了三位保护码:允许读写执行、对应位为 1,对应位为0,则表示不允许读写、执行。程序中使用的主要设计结构如下:主文件目录和用户文件目录( MFD、UFD)打开文件目录( AFD)(即运行文件目录)3.流程图与模块调用4.实验分析想要完成操作系统算法,首先要弄清楚操作系统相关的专业术语。弄清各个算法的流程和目的要求。才能模拟出相关算法的过程。一般情况下,操作系统中,文件管理提供了如下功能:①统一管理文件存储空间(即外存),实施存储空间的分配与回收。②确定文件信息的存放位置及存放形式。③实现文件从名字空间到外存地址空间的映射,即实现文件的按名存取。④有效实现对文件的各种控制操作(如建立、撤销、打开、关闭文件等)和存取操作(如读、写、修改、复制、转储等)其实就是对文件进行管理。本次实验对文件定义了如下数据结构:class file: def __init__(self, uid, fid, name, status): self.uid = uid self.fid = fid self.name = name self.status = status 定义了文件的id标识,文件名,状态等等。5.运行情况6.实验体会通过本次实验,我深刻的理解了操作系统中文件管理可视化的优点。操作系统实验重在理解每一个算法的意图和目的,那么就选择适当的数据结构模拟过程就可以完成相关算法了。文件管理系统作为一个统一的信息管理机制,可以解决海量文件存储,管理困难;查找缓慢,效率低下;文件版本管理混乱;文件安全缺乏保障;文件无法有效协作共享;知识管理举步维艰等问题。本次实验采用python完成,IDE是pycharm。【附】实验代码mfd = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'] # 存用户信息 ufd = [] # 存放文件 class file: def __init__(self, uid, fid, name, status): self.uid = uid self.fid = fid self.name = name self.status = status # 输出用户 def display1(): print("用户:\n1、A\t2、B\n3、C\t4、D\n5、E\t6、F\n7、G\t8、H\n9、I\t10、J\n") # 输出用户文件夹目录 def display2(arr): str = '该用户的文件目录为:\n' for index, item in enumerate(arr): if (index + 1) % 2: str += '{}、文件名:{} 文件fid:{} 文件状态:{}\t'.format((index + 1), item.name, item.fid, item.status) else: str += '{}、{}\n'.format((index + 1), item.name) print(str, '\n') # 输出命令表 def display3(): print('指令集:\n1、创建新文件\t2、删除文件\n3、打开文件\t4、关闭文件\n5、读出文件\t6、写入文件\n7、查看文件目录\t8、退出\n') display1() option = input('请输入用户名以进入UFD:') if option in mfd: files = [] # 存放属于该用户的文件 for item in ufd: if item.uid == option: files.append(item) display2(files) display3() option2 = int(input('请输入指令序号:')) while option2 != 8: if option2 == 1: if len(files) < 10: a = input('请输入新建的文件名:') p = file(option, len(files) + 1, a, 'close') ufd.append(p) files.append(p) option2 = int(input('请输入指令序号:')) else: print('最多只能创建10个文件') option2 = int(input('请输入指令序号:')) elif option2 == 2: b = int(input('请输入你需要删除文件的fid:')) for item in files: if item.fid == b: files.remove(item) print("已删除") option2 = int(input('请输入指令序号:')) elif option2 == 3: c = int(input('请输入你需要打开文件的fid:')) for item in files: if item.fid == c: item.status = 'open' print('已打开') option2 = int(input('请输入指令序号:')) elif option2 == 4: d = int(input('请输入你需要关闭文件的fid:')) for item in files: if item.fid == d: item.status = 'close' print('已关闭') option2 = int(input('请输入指令序号:')) elif option2 == 5: e = int(input('请输入你需要读取文件的fid:')) for item in files: if item.fid == e: item.status = 'read' print('已读取') option2 = int(input('请输入指令序号:')) elif option2 == 6: f = int(input('请输入你需要写入文件的fid:')) for item in files: if item.fid == f: item.status = 'write' print('已写入') option2 = int(input('请输入指令序号:')) elif option2 == 7: display2(files) option2 = int(input('请输入指令序号:')) else: print("无此用户!")
首先,帮小白理清一下思路,如果想看备案教程的请跳过:1、假如你想做一个人人都可以直接通过网址访问的网站,那么你需要去网上买一块虚拟空间,来存放你的代码。去哪买?阿里云、腾讯云、华为云等等这些提供云服务的公司官网。而你所买的这个虚拟空间,类似于百度网盘这样,我们形象的把它称之为——服务器。阿里云、腾讯云、华为云等都能买到,我是在阿里云买的轻量应用服务器,价格在¥1000左右一年。学生版的价格在¥100每年,注意去搜索学生版服务器当你购买之后,服务商会给你一个IP,可以在控制台配置一下镜像系统,然后即可正常使用了。目前你的网站都得以这种形式访问:http://47.103.21.63/hcqmcy/2、但是这样的IP很难记,很多场景下,都需要一个直截了当的域名,像百度的www.baidu.com.所以我们继续去买个中意的域名。域名也不是很贵,一般的域名几十到几百不等。3、买了域名之后,我们需要用某种技术,来把我们买的域名和我们的服务器建立起映射。这样,当我们访问域名的时候,浏览器会对于去访问映射服务器里面的内容。这个操作我们称之为——CDN解析。4、最后,建立起映射之后,我们仍然无法通过域名访问网站。因为我们还没有对我们的域名进行——备案。备案是国家规定的、管控互联网正规化的操作。备案分为ICP备案(又称管局备案)和公安备案。下面我们介绍企业网站备案教程我们来到备案首页。因为之前在没有备案过,所以点击首次备案。备案按照阿里的流程走,这里我主要介绍一下注意点。【注:】1)因为是企业网站,域名备案的时候,千万不要选个人主体,我当时怕麻烦,想注册成个人网站,然后放公司的官网信息,后来阿里的客服打电话给我说,不行必须得注册成企业主体。2)在注册企业主体的时候,得考虑你(作为网站负责人)和这个公司法人的关系。如果你不是公司的法人,那么你需要提供两个表:公司法人授权书和网站管理授权书。点击这里查看授权书3)罗列一下企业网站备案需要的信息:网站负责人的相关信息(电话、身份证号、邮箱、住址等等)、企业法人的相关信息(电话、身份证号、邮箱、住址等等)、企业的相关信息(地址、官方电话)、企业营业执照、域名的证书(可以去服务商哪里免费下载)、公司法人授权书和网站管理授权书(这个需要公司法人和网站负责人的姓名和身份证号和企业的公章)、网站负责人的身份证拍照、网站负责人拿着身份证的正面照4)再提醒一下,你以个人的名义买了域名之后,要去阿里云后台把这个域名过户给企业,不然审核不会通过。虽然过户了,但是域名仍然在你阿里云的账号里面,操作权还是在你。5)推荐大家下载app备案,速度更快6)提交之后,1-2工作日之内,客服会打电话给你确认你的身份,然后告诉你,你的网站在阿里云那边的初审结果。如果有问题那就继续改再提交,没有问题的话阿里那边会帮你提交给管局(也就是ICP备案)。7)我记得我那个时候。第一天,客服打了我6、7个电话,我改了好多次才没有问题;第二天,ICP那边就发消息给我说备案成功,我就拿到ICP备案号了;第三天,上午我去提交公安备案,下午拿到公安备案号。我们ICP备案号拿到之后,还需要公安备案。登录http://www.beian.gov.cn/portal/index 全国互联网安全管理服务平台。先注册账号,实名认证一下。点击如下侧边栏,办理申请业务。我是上午申请,下午通过的。当拿到ICP备案号和公安备案号之后,千万不要忘记,在你网站的底部贴出ICP备案号和公安备案号。用< a href=“你的链接”>< /a>.【注:】帮别人申请了官网之后,你的这个阿里云账号就不能再申请个人网站了,所以还是建议让法人注册一个账号,让法人来走这些流程。有问题可评论留言^_^。
写在前面:大家好,我是热爱编程的小泽。【建站系列教程】是我的亲身建站经历写给广大建站同胞们的教学博客。喜欢的话点个赞吧~ 评论区欢迎交流讨论~----------------------------------------Warning------------------------------------------------------只限于学习用途,请勿转载、商用,后果自负点击此处下载完整版:https://download.csdn.net/download/qq_43592352/12367798一、追书神器api这里是自己整理的追书神器接口分类带书籍数量的父分类url: http://api.zhuishushenqi.com/ranking/genderresponse:{ "male": [ { "name": "玄幻", "bookCount": 429247 }, { "name": "奇幻", "bookCount": 41711 } ], "female": [ { "name": "古代言情", "bookCount": 338664 }, { "name": "现代言情", "bookCount": 395887 } ... ] "press": [] } 带子分类的父分类url: http://api.zhuishushenqi.com/cats/lv2response:{ "male": [ { "major": "玄幻", "mins": [ "东方玄幻", "异界大陆", "异界争霸", "远古神话" ] }, { "major": "奇幻", "mins": [ "西方奇幻", "领主贵族", "亡灵异族", "魔法校园" ] } ] ... } 获取分类书籍(categoryInfo)request:query: { gender: 'male' // 性别 type: 'reputation' // 按照不同的类型获取分类下的书籍(hot, new, reputation, over) major: '玄幻' // 父分类 minor: '东方玄幻' // 子分类 start: 0 // 起始位置 limit: 20 //每页数量 }response: { _id: 书籍id title: 书籍名 author: 作者 shortIntro: 简介 cover: 封面 site: 书源 latelyFollower: 追书人数 retentionRatio: 好评率(%) lastChater: 最新章节 tag: 标签 }书籍书籍详情url: http://api.zhuishushenqi.com/book/:idrequest:url params: { id: BookId } response:{ "_id": "5106099abb1c67cf28000016", //书籍id "author": "禹枫", //作者 "cover": "/agent/http://images.zhulang.com/book_cover/image/18/98/189843.jpg", // 封面 "creater": "iPhone 4", "longIntro": "...", //长介绍 "title": "异世灵武天下", //书名 "cat": "东方玄幻", "majorCate": "玄幻", //主分类 "minorCate": "东方玄幻", //子分类 "_le": false, "allowMonthly": true, "allowVoucher": true, "allowBeanVoucher": true, "hasCp": true, "postCount": 3183, "latelyFollower": 43192, //追书人数 "followerCount": 5164, "wordCount": 11241234, //总字数 "serializeWordCount": 129762, //平均 "retentionRatio": "66.16", //好评率 "updated": "2017-01-19T05:58:53.799Z", //更新于 "isSerial": false, //连载中 "chaptersCount": 3577, //总章数 "lastChapter": "后续第五章:大结局终章", //最新章节 "gender": [ "male" ], "tags": [], "donate": false }书籍章节这部分相对比较复杂步骤如下:书籍id -> 获取所有书源 -> 书源id -> 获取章节目录 -> 章节link -> 章节内容url : http://api.zhuishushenqi.com/btocrequest:url params: { id: BookId }query string: { view: chapters } response: { "_id": "5881e82e3e3357fa266f6a3e", "name": "优质书源", "link": "http://vip.zhuishushenqi.com/toc/5881e82e3e3357fa266f6a3e", "book": "5779b38d3b433dd647d95da2", "chapters": [ { "title": "第一章 状元再世", // 章节名 "link": "http://vip.zhuishushenqi.com/chapter/5881e82e4e307ea47f89deeb?cv=1484908590347", //章节地址 "id": "5881e82e4e307ea47f89deeb", //章节id "currency": 10, //价格 "unreadble": false, "isVip": false // 是否是vip章节 } ], "updated": "2017-03-31T14:44:51.413Z", //更新于 "host": "vip.zhuishushenqi.com" // 书源 }章节内容url: http://chapter2.zhuishushenqi.com/chapter/:chapterLinkrequest:url params: { chapterLink: 'http://vip.zhuishushenqi.com/chapter/5881e82e4e307ea47f89df43' // 章节地址 }response:{ "ok": true, "chapter": { "title": "第八十九章 杂阿神功(二)", // 章节名 "body": "\n\r\n\r\n\r请安装最新版追书 以便使用优质资源", "isVip": true, "cpContent": "..", //章节内容 "currency": 10, "id": "5881e82e4e307ea47f89df43" } }作者的书籍url: http://api.zhuishushenqi.com/book/accurate-search?author=忘语request:url params: { author: 作者名 }response:{ "books": [ { "_id": "567d2cb9ee0e56bc713cb2c0", "title": "玄界之门", "author": "忘语", "shortIntro": "...", "cover": "/cover/148369972991098", "cat": "仙侠", "site": "zhuishuvip", "majorCate": "仙侠", "minorCate": "幻想修仙", "banned": 0, "latelyFollower": 35504, "followerCount": 0, "retentionRatio": 65.18, "lastChapter": "第919章 前线告急" }, ... ], "ok": true }排名排名分类url: http://api.zhuishushenqi.com/ranking/genderresponse:{ "female": [ { "_id": "54d43437d47d13ff21cad58b", //周榜 "title": "追书最热榜 Top100", "cover": "/ranking-cover/142319314350435", "collapse": false, "monthRank": "564d853484665f97662d0810", //月榜 "totalRank": "564d85b6dd2bd1ec660ea8e2" // 总榜 } }排名详情url: http://api.zhuishushenqi.com/ranking/:idrequest:url params: { id: 排名id //周榜等 }response:{ "ranking": { "_id": "54d42d92321052167dfb75e3", "updated": "2017-03-31T21:20:09.135Z", "title": "追书最热榜 Top100", "tag": "zhuishuLatelyFollowerMale", "cover": "/ranking-cover/142319144267827", "icon": "/cover/148945782817557", "__v": 790, "monthRank": "564d820bc319238a644fb408", "totalRank": "564d8494fe996c25652644d2", "created": "2017-04-01T03:20:20.988Z", "isSub": false, "collapse": false, "new": true, "gender": "male", "priority": 250, "books": [ { "_id": "51d11e782de6405c45000068", "author": "天蚕土豆", "cover": "/agent/http://image.cmfu.com/books/2750457/2750457.jpg", "shortIntro": "大千世界,位面交汇,万族林立,群雄荟萃,一位位来自下位面的天之至尊,在这无尽世界,演绎着令人向往的传奇,追求着那主宰之路。 无尽火域,炎帝执掌,万火焚苍穹。 武...", "title": "大主宰", "site": "zhuishuvip", "cat": "玄幻", "banned": 0, "latelyFollower": 359456, "retentionRatio": "45.31" } ] ... ok: true }书评讨论url: http://api.zhuishushenqi.com/post/by-book?&start=21&limit=20request:query strings: { book: {bookId}, sort: (updated|created|comment-count) // 排序方式 type: (normal,vote) // 未知 start, limit } response:{ "posts": [{ "_id": "59b25a1ca17d25ad324e208d", "author": { "_id": "54ef4d94704d6be45528af89", "avatar": "/avatar/34/bb/34bbc2992b34e6a042a83be1f6f3b735", //http://statics.zhuishushenqi.com "nickname": "追书家的小萝莉", "activityAvatar": "/activities/20170120/1.jpg", "type": "official", "lv": 9, "gender": "female" }, "type": "vote", "likeCount": 371, "block": "ramble", "haveImage": true, "state": "normal", "updated": "2017-09-16T05:38:16.092Z", "created": "2017-09-08T08:51:40.345Z", "commentCount": 5309, "voteCount": 3980, "title": "【真够刺激】答题拿红包!邀请好友满30元就能提现!★攻略真的不先看下么!" }], "ok": true } 短评url: http://api.zhuishushenqi.com/post/short-review/by-bookrequest:query strings: { book: {bookId}, sortType: (lastUpdated|newest|mostlike) //排序方式 start, limit } response:{ "docs": [ { "_id": "596affc7fe0ad34f1b8317e3", "rating": 3, "type": "short_review", "author": { "_id": "596ac9b85d0fe1b460155952", "avatar": "/avatar/bd/bf/bdbf666388552ebb3166473e3f689dfd", "nickname": "素心", "activityAvatar": "", "type": "normal", "lv": 4, "gender": "female" }, "book": { "_id": "51060c88bb1c67cf28000035", "cover": "/agent/http%3A%2F%2Fimg.1391.com%2Fapi%2Fv1%2Fbookcenter%2Fcover%2F1%2F23766%2F_23766_549079.jpg%2F", "title": "真灵九变" }, "likeCount": 2, "priority": 0.497, "block": "short_review", "state": "normal", "updated": "2017-08-06T09:58:26.733Z", "created": "2017-07-16T05:55:19.277Z", "content": "就是结尾有点烂尾了" } ], "ok": true } 书评url: http://api.zhuishushenqi.com/post/review/by-book?book=51060c88bb1c67cf28000035&sort=updated&start=0&limit=20request:query strings: { book: {bookId}, sort: (updated|created|comment-count), start, limit } response:{ "total": 35, "reviews": [{ "_id": "584201194fe8537c0f7fdf32", "rating": 1, "author": { "_id": "580cc42178afb3190f41f5ae", "avatar": "/avatar/b3/70/b370b0054ae878829bfae3fe8ceacf3e", "nickname": "……", "activityAvatar": "/activities/20170120/4.jpg", "type": "normal", "lv": 8, "gender": "male" }, "helpful": { "total": 35, "yes": 117, "no": 82 }, "likeCount": 5, "state": "normal", "updated": "2017-09-13T15:08:48.577Z", "created": "2016-12-02T23:17:45.711Z", "commentCount": 76, "content": "1)一边声明“猪脚资质一般”,一边又在没有“穿越神器”的情况下给猪脚开挂。修炼速度莫名其妙就比其他人快,这也是“资质一般”?能自圆其说不?\n\n2)明明是凡人流里那种勾心斗角杀人夺宝的世界,猪脚的朋友未免太多了吧?还各个都为猪脚着想,围着猪脚转?凡人流啊!那是神马鸡毛世界,那是人人都可能在你身后打闷棍的世界。想玩哥们弟兄义气江湖的,请出门左手见《飘渺之旅》下车。\n\n3)“俏皮、傲娇”的女主!我拉个擦,这个基本是所有YY书的鹤顶红了,本以为绝迹几千年,没想到还能见!\n\n4)师姐,不是1个,是10个!我进错门了吗?这是许仙传吗?真是许仙传就好了!这分明是睡裤外穿、浓妆艳抹的乡下小保姆嘛!\n\n5)美少妇师傅!你到底想写啥?玩后宫,人妻的请出门右转进晋江。\n\n6)N个陷害猪脚,差点让猪脚死球的同门,猪脚被坑了一次又一次,从来不报复,从来不想解决,于是被人从头坑到尾。请问,你是白求恩还是科利华?还是传说中的圣雄甘地?\n\n7)猪脚开挂升级也就罢了,猪脚的朋友们也是哥哥开挂。一开始书中声称“升溶血修士如何如何难,升锻蛋修士更是千中无一”,好,猪脚开挂,奇遇不断,十几年升到锻蛋!贫道以为这已经是牛逼透顶的了,结果再看,猪脚的朋友们也一个个都锻蛋了!这尼玛也是“如何如何难”?“千中无一”??拜托你学凡人流,看过凡人没有???更恶心的是,就连猪脚随便找得几个土匪小弟,也一个个吃猪尿泡一样升锻蛋了。我。。。。叉。。。。", "title": "个人观点,看书前最好看看!" }], "ok": true } 书单url: http://api.zhuishushenqi.com/book-listrequest:query string: { sort: (collectorCount|created), duration: (last-seven-days|all), gender: (male|female), tag: (有点多), start } 说明:本周最热的query是: sort=collectorCount&duration=last-seven-days&start=0最新发布是: sort=created&duration=all最多收藏是: sort=collectorCount&duration=allresponse:{ "total": 241518, "bookLists": [ { "_id": "57331505025ffaa06cb28852", "title": "★星光书局 ★(04-20更", "author": "人闲", "desc": "☆准星(不好看),★一星,★★二星,★★★三星,★★★★,★★★★★五星 (持续更新中……)……………本期歌单:周慧敏《自作多情》、赵雷《已是两条路上的人》、张韶涵《寓言》、张惠妹《我最亲爱的》、张惠妹《哭砂》、张惠妹《剪爱》、张碧晨《渡红尘》、Amy Winehouse《You know I'm no good》、邓紫棋《偶尔》、邓紫棋《喜欢你》、叶倩文《曾经心疼》、叶倩文《祝福》", "gender": "male", "collectorCount": 96298, "cover": "/agent/http%3A%2F%2Fimg.1391.com%2Fapi%2Fv1%2Fbookcenter%2Fcover%2F1%2F41678%2F_41678_412098.jpg%2F", "bookCount": 464 } ], "ok": true } 书单详情url: http://api.zhuishushenqi.com/book-list/:bookIdrequest:url params: { bookId: {bookId} }response:{ "bookList": { "_id": "57331505025ffaa06cb28852", "updated": "2017-05-25T03:18:20.437Z", "title": "★星光书局 ★(04-20更", "author": { "_id": "568dcb55f08722bf2bdeeb38", "avatar": "/avatar/41/32/41327b6d253592bb644fa4dd4c5c9b03", "nickname": "人闲", "type": "normal", "lv": 9 }, "desc": "☆准星(不好看),★一星,★★二星,★★★三星,★★★★,★★★★★五星 (持续更新中……)……………本期歌单:周慧敏《自作多情》、赵雷《已是两条路上的人》、张韶涵《寓言》、张惠妹《我最亲爱的》、张惠妹《哭砂》、张惠妹《剪爱》、张碧晨《渡红尘》、Amy Winehouse《You know I'm no good》、邓紫棋《偶尔》、邓紫棋《喜欢你》、叶倩文《曾经心疼》、叶倩文《祝福》", "gender": "male", "created": "2016-05-11T11:18:29.278Z", "tags": [ "热血", "都市", "现代" ], "stickStopTime": null, "isDraft": false, "isDistillate": false, "collectorCount": 96299, "books": [ { "book": { "cat": "东方玄幻", "_id": "579eaef492253c435235dbea", "title": "斗战狂潮", "author": "骷髅精灵", "longIntro": "双月当空,无限可能的英魂世界孤寂黑暗,神秘古怪的嬉命小丑百城联邦,三大帝国,异族横行,魂兽霸幽这是一个英雄辈出的年代,人类卧薪尝胆重掌地球主权,孕育着进军高纬度的野望!重点是……二年级的废柴学长王同学,如何使用嬉命轮盘,撬动整个世界,学妹们,请注意,学长来了!!!斗战一群:21222419(两千人战力群)骷髅的微信公共号:kuloujingling00新浪微博:骷髅精灵", "cover": "/agent/http%3A%2F%2Fimg.1391.com%2Fapi%2Fv1%2Fbookcenter%2Fcover%2F1%2F1286280%2F_1286280_696459.jpg%2F", "site": "zhuishuvip", "majorCate": "玄幻", "minorCate": "东方玄幻", "banned": 0, "latelyFollower": 26038, "wordCount": 1962241, "retentionRatio": 60.36 }, "comment": "★★二星…………" } ], "shareLink": "http://share.zhuishushenqi.com/booklist/57331505025ffaa06cb28852", "id": "57331505025ffaa06cb28852", "total": 464 }, "ok": true } 二、资源二api按分类查找图书url: 39.96.77.250/view/bookList?&category=2&page=1request:{ category(类型:玄幻、修真、都市、穿越、网游、科幻、完本、其他):1-8, page:Number } 按关键字查找图书url: 39.96.77.250/view/bookList?&keywords=&size=100request:{ keywords:String, size:Number } 获取小说目录url: 39.96.77.250/view/chapters?&bookId=2960request:{ bookId:Number }获取章节内容url: 39.96.77.250/view/readBook?&bookId=2960&chapterId=1request:{ bookId:Number, chapterId:Number }三、宜搜api关键字搜索url: http://api.easou.com/api/bookapp/searchdzh.m?word=道君&page_id=1&count=20&cid=eef_&os=ios&appverion=1049request:{ count:Number, page_id:Number, word:String } 四、结语本来我是想用这些api做网站的,后来网上查了很多。最后还是劝大家,盗版网站、触碰版权的网站大家不要做,不要以身试法。放弃做小说网站之后,这些api拿出来分享给大家。
【Java】eclipse如何导入项目1.第一步,打开eclipse,点击file->import2.第二步,选择general->existing projects into workspace (即常规-现有项目导入到工作空间)3.第三步,选择browse来选择被导入的文件。记住下方红框一定要勾选,才能把文件复制到eclipse的工作空间里面。
博主的话Three.js是js的一个3D引擎,比较复杂。比如光是Three.js就附带了100多个js插件,没有大毅力是学不下来的。最近比较浮躁,打算停止对Three.js的学习。做的3D飞机大战也停止不做了。我把目前做好的部分的代码贴出来。希望对大家有所用处。游戏完成度:做了背景、飞机、子弹、子弹循环以及消失贴图只能在服务器环境下显示,或者在下载了配置文件的Google浏览器也可以运行点击下载:https://download.csdn.net/download/qq_43592352/12374711运行图片目录路径index.html<html> <head> <title>one</title> <script src="JS/three.min.js" type="text/javascript"></script> <script src="JS/jquery-3.3.1.min.js" type="text/javascript"></script> <style> body{ margin: 0; overflow: hidden; } </style> </head> <body > <div id="WebGL-output"> </div> </body> <script> //声明对象 var containerX=115,containerY=50; //容器,飞机活动范围 var camera; //相机 var scene; //场景 var renderer; //渲染器 var airPlane,planeX=0,planeY=50,planeSpeed=1; init(); //初始化 animate(); /* 场景 */ function initScene() { scene = new THREE.Scene(); //scene.fog=new THREE.Fog(0xffffff,0.015,100);//雾化效果 } /* 相机 */ function initCamera() { camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 10000); camera.position.set(0,200, 0); camera.lookAt(new THREE.Vector3(0, 0, 0)); } /* 渲染器 */ function initRender() { renderer = new THREE.WebGLRenderer({antialias: true}); renderer.setSize(window.innerWidth, window.innerHeight); renderer.setClearColor(new THREE.Color(0xEEEEEE));//背景颜色 document.body.appendChild(renderer.domElement); renderer.shadowMapEnabled = true; } /* 灯光 */ function initLight() { scene.add(new THREE.AmbientLight(0x0c0c0c));//环境光,不产生阴影,弱化阴影,给场景提供一些其他的颜色 let spotLight = new THREE.SpotLight(0xffffff);//聚光灯,类似台灯 spotLight.position.set(-100, 200, 0); spotLight.castShadow = true; // 让光源产生阴影 let spotLight2 = new THREE.SpotLight(0xffffff); spotLight2.position.set(800, 800, 800); scene.add(spotLight); scene.add(spotLight2); } /*************************函数封装部分****************************/ function GetTexture(imgUrl,wNum,hNum){//设置贴图的图片和重复频率 var textureGrass = THREE.ImageUtils.loadTexture(imgUrl); textureGrass.wrapS = THREE.RepeatWrapping; textureGrass.wrapT = THREE.RepeatWrapping; textureGrass.repeat.set(wNum, hNum); return textureGrass; } function checkBullet(p){ if(p!=null) { p.bullet.rotation.z=step; p.bulletY-=planeSpeed/2; p.bullet.position.z=p.bulletY; if(p.bulletY<=-45) { scene.remove(p.bullet); p=null; } } } /*********************************************************************/ function AirPlane() { this.mesh = new THREE.Object3D(); // 这里要做的是一个驾驶舱 var geomCockpit = new THREE.BoxGeometry(80,50,50,1,1,1); var matCockpit = new THREE.MeshPhongMaterial({color:0x74983E, shading:THREE.FlatShading,map: GetTexture('fontPic/bg.jpg',1,1)}); geomCockpit.vertices[4].y-=10; geomCockpit.vertices[4].z+=20; geomCockpit.vertices[5].y-=10; geomCockpit.vertices[5].z-=20; geomCockpit.vertices[6].y+=20; geomCockpit.vertices[6].z+=20; geomCockpit.vertices[7].y+=20; geomCockpit.vertices[7].z-=20; var cockpit = new THREE.Mesh(geomCockpit, matCockpit); cockpit.castShadow = true; cockpit.receiveShadow = true; this.mesh.add(cockpit); // 还要有引擎盖 var geomEngine = new THREE.BoxGeometry(20,50,50,1,1,1); var matEngine = new THREE.MeshPhongMaterial({color:0xBCB46B, shading:THREE.FlatShading}); var engine = new THREE.Mesh(geomEngine, matEngine); engine.position.x = 40; engine.castShadow = true; engine.receiveShadow = true; this.mesh.add(engine); // 做个尾巴吧 var geomTailPlane = new THREE.BoxGeometry(15,20,5,1,1,1); var matTailPlane = new THREE.MeshPhongMaterial({color:0xff0000, shading:THREE.FlatShading}); var tailPlane = new THREE.Mesh(geomTailPlane, matTailPlane); tailPlane.position.set(-35,25,0); tailPlane.castShadow = true; tailPlane.receiveShadow = true; this.mesh.add(tailPlane); // 机翼当然少不了,用长长的矩形穿过机身,多么美妙! var geomSideWing = new THREE.BoxGeometry(40,8,150,1,1,1); var matSideWing = new THREE.MeshPhongMaterial({color:0x74983E, shading:THREE.FlatShading,map: GetTexture('fontPic/plane1.jpg',1,5)}); var sideWing = new THREE.Mesh(geomSideWing, matSideWing); sideWing.castShadow = true; sideWing.receiveShadow = true; this.mesh.add(sideWing); // 飞机前端旋转的螺旋桨 var geomPropeller = new THREE.BoxGeometry(20,10,10,1,1,1); var matPropeller = new THREE.MeshPhongMaterial({color:0xff0000, shading:THREE.FlatShading}); this.propeller = new THREE.Mesh(geomPropeller, matPropeller); this.propeller.castShadow = true; this.propeller.receiveShadow = true; // 螺旋桨 var geomBlade = new THREE.BoxGeometry(1,100,20,1,1,1); var matBlade = new THREE.MeshPhongMaterial({color:0xff00aa, shading:THREE.FlatShading}); blade = new THREE.Mesh(geomBlade, matBlade); blade.position.set(8,0,0); blade.castShadow = true; blade.receiveShadow = true; this.propeller.add(blade); this.propeller.position.set(50,0,0); this.mesh.add(this.propeller); }; function createBullet() { // 子弹 this.bulletX=planeX; this.bulletY=planeY-20; var geomSideWing = new THREE.BoxGeometry(2,2,4,1,1,1); var matSideWing = new THREE.MeshPhongMaterial({color:0x74983E, shading:THREE.FlatShading}); this.bullet = new THREE.Mesh(geomSideWing, matSideWing); this.bullet.castShadow = true; this.bullet.receiveShadow = true; this.bullet.position.set(this.bulletX,30,this.bulletY); scene.add(this.bullet); }; function createPlane(){ airplane = new AirPlane(); airplane.mesh.scale.set(.25,.25,.25); airplane.mesh.rotation.y=0.5*Math.PI; airplane.mesh.position.set(0,30,planeY); scene.add(airplane.mesh); } /* 场景中的内容 */ function initContent() { /*创建背景平面*/ var BgMaterial = new THREE.MeshLambertMaterial({map: GetTexture('fontPic/bg.jpg',1,1)});// 设置背景平面的颜色、透明度、贴图等 var BgGeometry = new THREE.PlaneGeometry(330,220,1,1);// 设置背景平面宽高,宽330、高220 // BgMaterial.side=THREE.DoubleSide;//使平面的反面也可以显示,这里看不到反面所以不需要 var Bg = new THREE.Mesh(BgGeometry, BgMaterial); // 创建平面 Bg.receiveShadow = true; //平面接收投影 Bg.rotation.x = -0.5*Math.PI; // 绕x轴旋转90度 Bg.position.set(0,0,0); // 平面坐标位置 scene.add(Bg); createPlane(); } var step=0,fpsnum=0; var enemyPlane0=null,enemyPlane1=null,enemyPlane2=null,enemyPlane3=null,enemyPlane4=null,enemyPlane5=null; var p0=null,p1=null,p2=null,p3=null,p4=null,p5=null; /* 组件动画 */ function action() { blade.rotation.x=step;//飞机螺旋桨的旋转 checkBullet(p0); checkBullet(p1); checkBullet(p2); checkBullet(p3); checkBullet(p4); checkBullet(p5); if(fpsnum==25) p0=new createBullet(); else if(fpsnum==50) p1=new createBullet(); else if(fpsnum==75) p2=new createBullet(); else if(fpsnum==100) p3=new createBullet(); else if(fpsnum==125) p4=new createBullet(); else if(fpsnum==150) p5=new createBullet(); else if(fpsnum>=151){ fpsnum=0; } fpsnum++; step+=planeSpeed/5; } /* 数据更新 */ function update() { action(); } /* 窗口变动触发 */ function onWindowResize() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); } /* 主函数-初始化 */ function init() { initScene(); initCamera(); initRender(); initLight(); initContent(); window.addEventListener('resize',onWindowResize,false)//添加全局监听器:尺寸改变 } /* 循环渲染 */ function animate() { requestAnimationFrame(animate); renderer.render(scene, camera); update(); } /*键盘监听事件*/ $(document).keydown(function(event){ if(event.which==37&&planeX>-containerX){ planeX-=planeSpeed; airplane.mesh.position.set(planeX,30,planeY); } if(event.which==39&&planeX<containerX){ planeX+=planeSpeed; airplane.mesh.position.set(planeX,30,planeY); } if(event.which==38&&planeY>-containerY){ planeY-=planeSpeed; airplane.mesh.position.set(planeX,30,planeY); } if(event.which==40&&planeY<containerY){ planeY+=planeSpeed; airplane.mesh.position.set(planeX,30,planeY); } }); </script> </html>
博主的话 这里博主将展示此游戏的html和js部分的代码,主要是对这些代码的讲解,教大家如何编写这样一个简单的 h5小游戏。希望大家在认真看完博主的文章后,不但能编写“是男人就下一百层”,还可以发散思维,编写更多有 趣的小游戏。 如果想要跑程序的话,还需要css代码和图片资源;在js里面还用到了jq和lufylegend框架,这是开源的, 大家可以到网上下载。 对于要css代码和img资源的,下载地址:https://download.csdn.net/download/qq_43592352/12368545游戏展示看图说话游戏初始界面:游戏开始:编程工具介绍1’ Google浏览器。 我是在Google上跑的游戏,如果大家拿我的文件参照的话,尽量用Google。我做的时候没有考虑兼容性 ,觉得一个小游戏,没必要。 2’VS Code。 这个没什么要求的,我用的是VS Code,只要可以编html、css、js,什么都可以,个人喜好。 3’JQuery和lufylegend插件。 简单的说,它们两个也是js文件。只是被封装起来,方便各种功能的调用。 用的话<script src="js/lufylegend-1.10.1.min.js"></script> <script src="js/jquery-3.3.1.js"></script> 直接在相关的文件里引用就可以了。 4’JQuery介绍 jq简单来说就是对原生js的简化,使js的使用更加方便快捷。 5’lufylegend介绍 lufylegend是一种游戏开发引擎,它是通过html的canvas标签来操作的。里面有一些方法很好用,但是, 它的排版和监听和js一比实在是太麻烦,于是我就抛弃了lufylegend。我的js代码只用了lufylegend一点点的 游戏动作封装的函数。 而且,用lufylegend只是身边正好有一本它的书。个人觉得lufylegend不是最好的游戏引擎。大家对游戏 感兴趣的话,不妨去网上找一些更大众的开源引擎。 游戏代码<html> <link rel="stylesheet" type="text/css" href="css/index.css"/> <script src="js/lufylegend-1.10.1.min.js"></script> <script src="js/jquery-3.3.1.js"></script> <body> <div id="container"> <div id="game"> <audio id="player" src=""></audio> <div id="info"> <div id="info_life"></div> <div id="info_score"> <div class="info_score_num"><img src="css/img/0.png"></div> <div class="info_score_num"><img src="css/img/0.png"></div> <div class="info_score_num"><img src="css/img/0.png"></div> <div class="info_score_num"><img src="css/img/0.png"></div> </div> </div> <div id="main"> <div id="menu"> <div id="menu_title"><text>是男人就下100层</text></div> <div id="menu_hero"> <div id="menu_hero1"><img src="css/img/left.png"/></div> <div id="menu_hero2"></div> <div id="menu_hero3"><img src="css/img/right.png"/></div> </div> <div id="menu_button"> <button id="menu_button1">开始游戏</button><br/> <button id="menu_button2">排行榜</button> </div> </div> <div id="gamemain"> <div id="hero"></div> <div id="game_bg2"><img src="css/img/game_bg2.png"></div> </div> <div id="ranking"> <text>排行榜</text> <div id="rank_table"> <dl> <dt>排名</dt> <dd>1</dd> <dd>2</dd> <dd>3</dd> <dd>4</dd> <dd>5</dd> </dl> <dl> <dt>英雄大名</dt> <dd></dd> <dd></dd> <dd></dd> <dd></dd> <dd></dd> </dl> <dl> <dt>层数</dt> <dd></dd> <dd></dd> <dd></dd> <dd></dd> <dd></dd> </dl> </div> </div> </div> </div> </div> </body> <script> /**************************便捷函数**************************************/ function getRandom(min,max){//获取在区间[min.max]内的int数 var s; s=parseInt(Math.random()*max+1); while(s<min) { s=parseInt(Math.random()*max+1); } return s; } function onIt(hx,hy,fx,fy){ if(hx<=fx+60&&hx>=fx-32&&hy>=fy-32&&hy<=fy-31) return 1; else return 0; } var shansuoTimes=0; function shansuo(){//闪烁3次 shansuoTimes++; $('#hero').fadeTo(200,0.1); $('#hero').fadeTo(200,1); if(shansuoTimes<=3) setTimeout("shansuo()",400); else shansuoTimes=0; } function sleep(ms){//时间延迟函数 return new Promise(resolve =>setTimeout(resolve,ms)) } async function test() { console.log('Hello') await sleep(1000) console.log('world!') } test(); /**************************基础页面操作设置**************************************/ var hero_href=new Array(),hero_href_key=1; for(var i=1;i<=15;i++){ hero_href[i]="css/hero/"+i+".png"; } //js改变网页页面大小 document.body.style.zoom=.8; //控制menu、gamemain、ranking的显示 $("#menu_button1").click(function(){ $("#menu").css('display','none'); $("#gamemain").css('display','block'); $("#ranking").css('display','none'); heroDie(); init(0,"hero",32,32,main); check(); heroDown(); myfloor(); displayScore(); displayLife(); }) $("#menu_button2").click(function(){ $("#menu").css('display','none'); $("#gamemain").css('display','none'); $("#ranking").css('display','block'); }) //控制英雄的切换 $("#menu_hero1 img").click(function(){ if(hero_href_key>1) hero_href_key--; heroDie(); main(); }) $("#menu_hero3 img").click(function(){ if(hero_href_key<15) hero_href_key++; heroDie(); main(); }) //分数显示 var score_pic=new Array('css/img/0.png','css/img/1.png','css/img/2.png','css/img/3.png','css/img/4.png','css/img/5.png','css/img/6.png','css/img/7.png','css/img/8.png','css/img/9.png',); var score=0; function displayScore(){ var s=score; var score0=s%10; var score1=parseInt(s/10)%10; var score2=parseInt(s/100)%10; var score3=parseInt(s/1000); $(".info_score_num img").eq(3).attr('src',score_pic[score0]); $(".info_score_num img").eq(2).attr('src',score_pic[score1]); $(".info_score_num img").eq(1).attr('src',score_pic[score2]); $(".info_score_num img").eq(0).attr('src',score_pic[score3]); setTimeout('displayScore()',50); } //生命条显示 var deadflag=false; function displayLife(){ isdie(); $("#info_life").css('width',nowHP*8+'px'); if(nowHP<=4&&nowHP>0) $("#info_life").css('background','red'); if(nowHP==0&&deadflag==false) {playMusic(1);deadflag=true;} setTimeout('displayLife()',50); } function playMusic(i) { var player = $("#player")[0]; if(i==1) { player.src='css/sound/dead.mp3'; player.play(); } } /**************************游戏设置**************************************/ /***lufylegend框架封装英雄类*****/ var loader,anime,layer; init(50,"menu_hero2",32,32,main); function main(){ loader=new LLoader(); loader.addEventListener(LEvent.COMPLETE,loadBItmapdata); loader.load(hero_href[hero_href_key],"bitmapData"); } function loadBItmapdata(event){ var bitmapdata=new LBitmapData(loader.content,0,0,64,64); var list=LGlobal.divideCoordinate(128,128,4,4); layer=new LSprite(); addChild(layer); anime=new LAnimation(layer,bitmapdata,list); layer.addEventListener(LEvent.ENTER_FRAME,onframe); } function onframe(){ $(document).keydown(function(event){ if(event.which==39){ anime.setAction(2); } else if(event.which==37){ anime.setAction(1); } }); $(document).keyup(function(event){ anime.setAction(0); }); anime.onframe(); } function heroDie(){ layer.die(); layer.removeAllChild(); } /***封装游戏背景类*****/ var bgHeight=0; function background(){ bgHeight+=2; $("#gamemain").css('background','url("css/img/gamemain_bg.png") 0px '+bgHeight+'px'); if(bgHeight>=550) bgHeight=0;//防止bgHeight过大 setTimeout('background()',50); } background(); /*********封装地板****************/ function floor(){ var that=this; var img=$(new Image()); var floorx=Math.random()*370; var floory=530; this.createFloor1=function(){//地板1类 img.attr({ 'src':'css/img/floor1.png', 'class':'floor', 'relative':0, 'type':1 }); img.css({ 'margin-left':floorx+'px', 'margin-top':floory+'px', }); $("#gamemain").append(img); this.run(); } this.createFloor2=function(){//地板2类 img.attr({ 'src':'css/img/floor2.png', 'class':'floor', 'relative':10, 'type':1 }); img.css({ 'margin-left':floorx+'px', 'margin-top':floory+'px', }); $("#gamemain").append(img); this.run(); } this.createFloor3=function(){//地板3类 img.attr({ 'src':'css/img/floor3.png', 'class':'floor', 'relative':0, 'type':1 }); img.css({ 'margin-left':floorx+'px', 'margin-top':floory+'px', }); $("#gamemain").append(img); this.run(); } this.createFloor4=function(){//地板4类 会消失的地板 img.attr({ 'src':'css/img/floor4in1.png', 'class':'floor', 'relative':0, 'type':2 }); img.css({ 'margin-left':floorx+'px', 'margin-top':floory+'px', }); $("#gamemain").append(img); this.run(); } this.createFloor5=function(){//地板5类 会滚动的地板 img.attr({ 'src':'css/img/floor5.gif', 'id':'floor5', 'class':'floor', 'relative':0, 'type':3 }); img.css({ 'margin-left':floorx+'px', 'margin-top':floory+'px', }); $("#gamemain").append(img); this.run(); } this.run=function(){ if(floory>=-20) { floory-=2; img.css('margin-top',floory+'px'); setTimeout(that.run,20); } else { img.css('display','none'); img.remove(); score++; } } } async function changefloor4($ppp) {//地板4类消失函数 $ppp.attr('src','css/img/floor4in2.png') await sleep(300) $ppp.attr("src","css/img/floor4in3.png") await sleep(300) $ppp.css('display','none'); $ppp.remove(); score++; ontheFloor=false;heroDown();clearTimeout(up); } async function floor5Move1(fx){//地板5类左移英雄 while(position[0]+20>fx){ $('#hero').css('margin-left',position[0]+'px'); position[0]-=2; await sleep(40); } ontheFloor=false;heroDown();clearTimeout(up); } function myfloor(){//创建地板 var ppp=new floor(); if(getRandom(1,5)==1) ppp.createFloor1(); else if(getRandom(1,5)==2) ppp.createFloor2(); else if(getRandom(1,5)==3) ppp.createFloor3(); else if(getRandom(1,5)==4) ppp.createFloor4(); else if(getRandom(1,5)==5) ppp.createFloor5(); setTimeout('myfloor()',500); } /*********封装英雄(未封装原因:lufylegend插件已经生成了英雄)****************/ var position=new Array(0,10);//全局变量,hero的位置坐标 var maxHP=12,nowHP=12;//全局变量,最大HP,现在HP var ontheFloor=false;//全局变量,是否在地板上 var gspeed=2;//全局变量,英雄下落速度,可随游戏时间增加 $(document).keydown(function(event){//对英雄移动的键盘操作 if(event.which==37){ ((position[0]-3)<0)?position[0]=0:position[0]-=3; $("#hero").css('margin-left',position[0]+'px'); } else if(event.which==39){ ((position[0]+3)>398)?position[0]=398:position[0]+=3; $("#hero").css('margin-left',position[0]+'px'); } }); var down,up,move; function heroDown(){//英雄自由下落 position[1]+=gspeed; $("#hero").css('margin-top',position[1]+'px'); down=setTimeout("heroDown()",20); } function heroUp(){//英雄随地板上升,此函数与floor类的run函数数值同步 position[1]-=2; $("#hero").css('margin-top',position[1]+'px'); up=setTimeout("heroUp()",20); } function isdie(){//对英雄向上触碰、向下掉底直接死亡,设置nowHP=0,然后交给‘生命条显示’函数来做英雄死亡后的事件 var y=parseInt($("#hero").css('margin-top')); if(y>=520||y<=9) nowHP=0; } /************************对是否落到地板上进行判断、处理****************************/ var StandX; function check(){ var allfloor=$(".floor"); var n=allfloor.length; for(var i=0;i<=n;i++){ var fx=parseInt(allfloor.eq(i).css("margin-left")); var fy=parseInt(allfloor.eq(i).css("margin-top")); var relativey=parseInt(allfloor.eq(i).attr("relative"));//将木板的relative值转化为int型 var type=parseInt(allfloor.eq(i).attr("type"));//将木板的type值转化为int型 var hx=position[0],hy=position[1]; if(onIt(hx,hy,fx,fy)&&(ontheFloor==false)) { ontheFloor=true; clearTimeout(down); position[1]+=relativey;//每个地板加了一个属性relative,是人物相对于木板的距离,方便带刺木板人物位置摆放 if(relativey==10) {nowHP--;shansuo();} if(type==2){ changefloor4(allfloor.eq(i)); } else if(type==3){ floor5Move1(fx); } heroUp(); StandX=fx; } if((hx<StandX-32||hx>StandX+60)&&(ontheFloor==true)) {ontheFloor=false;heroDown();clearTimeout(up);} } setTimeout("check()",.5); } /************************对排行榜相关数据处理****************************/ /*var fso=new XMLHttpRequest(Scripting.FileSystemObject); var f=fso.createtextfile("data.txt",2,true); f.writeLine("wo shi di yi hang"); */ </script> </html> 代码讲解我尽量把讲解的重心放在游戏编程思想和js上面。 首先是html的节点结构。无论是编游戏,还是编网页,编程之前做好html节点的树结构是十分重要的事情。如图:dom结构大概是这样,方便大家搞清关系。container这是一个容器div,背景是截图中的那张山水图片。然后我把这个div居中,其他元素都写在container里面。game这也是一个容器div,背景如下:让game居container的中。info这里面会显示生命条和分数。mainmain里面有三个div,这才是核心部分。第一个div是菜单,第二个是游戏主页,第三个是排行榜。其次是js代码的编写,这是核心。我们把main的宽高限制在#game(以后的div用#id替代)背景的第三个白色透明框。同样的,#menu,#gamemain,#ranking的宽高也是。js 第一步 切换div的显示与隐藏当我们刚开始打开游戏,出现的应该是菜单页面,在点击“开始游戏”“排行榜”之后,会切换到相应的div。即显示相应的div,隐藏其他的div。//控制menu、gamemain、ranking的显示 $("#menu_button1").click(function(){ $("#menu").css('display','none'); $("#gamemain").css('display','block'); $("#ranking").css('display','none'); }) $("#menu_button2").click(function(){ $("#menu").css('display','none'); $("#gamemain").css('display','none'); $("#ranking").css('display','block'); }) 对相应的按钮点击后,对不同的div进行显示和隐藏。js 第二步 在菜单页面用lufylegend插件封装英雄/***lufylegend框架封装英雄类*****/ var loader,anime,layer; init(50,"menu_hero2",32,32,main); function main(){ loader=new LLoader(); loader.addEventListener(LEvent.COMPLETE,loadBItmapdata); loader.load(hero_href[hero_href_key],"bitmapData"); } function loadBItmapdata(event){ var bitmapdata=new LBitmapData(loader.content,0,0,64,64); var list=LGlobal.divideCoordinate(128,128,4,4); layer=new LSprite(); addChild(layer); anime=new LAnimation(layer,bitmapdata,list); layer.addEventListener(LEvent.ENTER_FRAME,onframe); } function onframe(){ $(document).keydown(function(event){ if(event.which==39){ anime.setAction(2); } else if(event.which==37){ anime.setAction(1); } }); $(document).keyup(function(event){ anime.setAction(0); }); anime.onframe(); } function heroDie(){ layer.die(); layer.removeAllChild(); } 这里值得注意的是:init(50,"menu_hero2",32,32,main);此init()方法表示在#menu_hero2为id的div里面建立一个宽高都为32px的canvas标签,整体速度是50ms一次,回调函数是main()。【建议大家去看一下lufylegend的官方API】在main()函数中有如下代码:loader.load(hero_href[hero_href_key],"bitmapData"); 【划重点!!!】这里hero_href[hero_href_key]是一个存放我本地英雄图片url的数组。在main()函数中又调用了loadBItmapdata()函数。loadBItmapdata()函数里有如下代码:javascriptvar list=LGlobal.divideCoordinate(128,128,4,4); 【划重点!!!】这是对图片进行分割。长宽都为128px的图片,进行4x4的分割。如下图。这种RPG小游戏的人物图片都是这样的,经过插件封装后会自动循环播放(这里播放速度就和init()函数里面的参数有关啦)。如果自己用js代码写的话,不一定可以写出来,而且也没有人家的好。好了,到这里,lufylegend的使命已经结束了,接下来都是js的功劳了。js 第三步 在菜单页面实现挑选英雄带大家理清思路。我们要实现的是#menu、#gamemain、#ranking三部分。现在我们已经把#gamemain、 #ranking的display设置为none了,它们消失了,只有#menu存在,所以我们专心做菜单的业务。 大家回到最上方看一下我菜单的样式,css我就不再说了,很简单的。在英雄两边设置了两个按钮来改变英雄的样式。这个要怎么实现呢?var hero_href=new Array(),hero_href_key=1; for(var i=1;i<=15;i++){ hero_href[i]="css/hero/"+i+".png"; } //控制英雄的切换 $("#menu_hero1 img").click(function(){ if(hero_href_key>1) hero_href_key--; heroDie(); main(); }) $("#menu_hero3 img").click(function(){ if(hero_href_key<15) hero_href_key++; heroDie(); main(); }) 1’定义一个数组hero_href[]用来存放"css/hero/1.png",“css/hero/2.png”……这样的字符,注意我这里的字符就是我用js访问图片的路径,大家要设置好。2’定义一个hero_href_key。当点击按钮的时候,hero_href_key会加一或者减一。3’调用heroDie()函数把页面清空,然后再重新调用main(),但是此时的图片url已经改变,所以英雄会相应改变。js 第四步 在游戏页面生成英雄这时候菜单页面已经做完了,我们把#menu的display设为none,#gamemain的display设为block。方便调试, 不然每次都要点击一下“开始游戏”按钮。接下来,我们开始做游戏模块! 同样的,在点击“开始游戏”按钮后,插件会清除画布,并在#hero里面生成英雄。这时,init()的参数速度不能为>0,因为多次调用init,它的速度参数会叠加。//控制menu、gamemain、ranking的显示 $("#menu_button1").click(function(){ $("#menu").css('display','none'); $("#gamemain").css('display','block'); $("#ranking").css('display','none'); heroDie(); init(0,"hero",32,32,main); }) 定义全局变量,position[0]为英雄的x,position[1]为英雄的y;监听键盘的左键和右键控制英雄左右移动,并且不可以移出#gamemain之外。var position=new Array(0,10);//全局变量,hero的位置坐标 $(document).keydown(function(event){//对英雄移动的键盘操作 if(event.which==37){ ((position[0]-3)<0)?position[0]=0:position[0]-=3; $("#hero").css('margin-left',position[0]+'px'); } else if(event.which==39){ ((position[0]+3)>398)?position[0]=398:position[0]+=3; $("#hero").css('margin-left',position[0]+'px'); } }); js 第五步 在游戏页面制作滚动的背景/***封装游戏背景类*****/ var bgHeight=0; function background(){ bgHeight+=2; $("#gamemain").css('background','url("css/img/gamemain_bg.png") 0px '+bgHeight+'px'); if(bgHeight>=550) bgHeight=0;//防止bgHeight过大 setTimeout('background()',50); } background(); 这里的技巧主要在css的background上,大家不妨去查一下background的参数。首先,我设置background是可以repeat的(没写默认可以),然后再定义y方向上的偏移量。最后每50ms执行一次其y上的偏移量加2。【值得注意的是,虽然背景图片是一张,但是拼接起来要看不出来分界线才行,要不然滚动也没意义】js 第六步 在游戏页面添加地板!/*********封装地板****************/ function floor(){ var that=this; var img=$(new Image()); var floorx=Math.random()*370; var floory=530; this.createFloor1=function(){//地板1类 img.attr({ 'src':'css/img/floor1.png', 'class':'floor', 'relative':0, 'type':1 }); img.css({ 'margin-left':floorx+'px', 'margin-top':floory+'px', }); $("#gamemain").append(img); this.run(); } this.createFloor2=function(){//地板2类 img.attr({ 'src':'css/img/floor2.png', 'class':'floor', 'relative':10, 'type':1 }); img.css({ 'margin-left':floorx+'px', 'margin-top':floory+'px', }); $("#gamemain").append(img); this.run(); } this.createFloor3=function(){//地板3类 img.attr({ 'src':'css/img/floor3.png', 'class':'floor', 'relative':0, 'type':1 }); img.css({ 'margin-left':floorx+'px', 'margin-top':floory+'px', }); $("#gamemain").append(img); this.run(); } this.createFloor4=function(){//地板4类 会消失的地板 img.attr({ 'src':'css/img/floor4in1.png', 'class':'floor', 'relative':0, 'type':2 }); img.css({ 'margin-left':floorx+'px', 'margin-top':floory+'px', }); $("#gamemain").append(img); this.run(); } this.createFloor5=function(){//地板5类 会滚动的地板 img.attr({ 'src':'css/img/floor5.gif', 'id':'floor5', 'class':'floor', 'relative':0, 'type':3 }); img.css({ 'margin-left':floorx+'px', 'margin-top':floory+'px', }); $("#gamemain").append(img); this.run(); } this.run=function(){ if(floory>=-20) { floory-=2; img.css('margin-top',floory+'px'); setTimeout(that.run,20); } else { img.css('display','none'); img.remove(); score++; } } } 首先,我们来封装个地板类。我们把地板的x坐标设为随机,y坐标固定在底部。然后,不同的createfloor函数,它们的url不同(即图片的样式,是地板还是尖刺还是弹簧……),relative不同(针对尖刺地板,假设图片高是20px,对于一般木板,英雄可以直接站上去,但是带刺的木板,英雄要踩刺,得再往下站一些,所以定义relative相当于木板和英雄的相对距离),type不同(不同类型木板,英雄站上去之后有不同的函数去执行)。最后,我们让每一个createfloor函数都去执行run函数,来控制木板上升,当木板上升到最上方时,把木板删去,分数+1。function myfloor(){//创建地板 var ppp=new floor(); if(getRandom(1,5)==1) ppp.createFloor1(); else if(getRandom(1,5)==2) ppp.createFloor2(); else if(getRandom(1,5)==3) ppp.createFloor3(); else if(getRandom(1,5)==4) ppp.createFloor4(); else if(getRandom(1,5)==5) ppp.createFloor5(); setTimeout('myfloor()',500); } 每隔0.5秒随机创建五种木板里的一种出来。js 第七步 定义英雄上升、下降、死亡函数首先这里面的数值不是随便设置的,不然没有游戏体验的。封装出英雄上升,下降,死亡函数。在踩到地板时上升;脚下没有地板时下降,碰到上方的刺或者掉落判断死亡。var down,up,move; function heroDown(){//英雄自由下落 position[1]+=gspeed; $("#hero").css('margin-top',position[1]+'px'); down=setTimeout("heroDown()",20); } function heroUp(){//英雄随地板上升,此函数与floor类的run函数数值同步 position[1]-=2; $("#hero").css('margin-top',position[1]+'px'); up=setTimeout("heroUp()",20); } function isdie(){//对英雄向上触碰、向下掉底直接死亡,设置nowHP=0,然后交给‘生命条显示’函数来做英雄死亡后的事件 var y=parseInt($("#hero").css('margin-top')); if(y>=520||y<=9) nowHP=0; } js 第八步 英雄与地板开始作用var StandX; function check(){ var allfloor=$(".floor"); var n=allfloor.length; for(var i=0;i<=n;i++){ var fx=parseInt(allfloor.eq(i).css("margin-left")); var fy=parseInt(allfloor.eq(i).css("margin-top")); var relativey=parseInt(allfloor.eq(i).attr("relative"));//将木板的relative值转化为int型 var type=parseInt(allfloor.eq(i).attr("type"));//将木板的type值转化为int型 var hx=position[0],hy=position[1]; if(onIt(hx,hy,fx,fy)&&(ontheFloor==false)) { ontheFloor=true; clearTimeout(down); position[1]+=relativey;//每个地板加了一个属性relative,是人物相对于木板的距离,方便带刺木板人物位置摆放 if(relativey==10) {nowHP--;shansuo();} if(type==2){ changefloor4(allfloor.eq(i)); } else if(type==3){ floor5Move1(fx); } heroUp(); StandX=fx; } if((hx<StandX-32||hx>StandX+60)&&(ontheFloor==true)) {ontheFloor=false;heroDown();clearTimeout(up);} } setTimeout("check()",.5); } 这算是核心代码了。解释如下:1,定义一个check函数,每0.5ms执行一次,一秒2000次。 2,每次都干什么事情呢? 每次都获取当前页面存在的地板对象,放在一个数组里面。然后用for循环遍历(暴力方式,所以是小游戏,大游戏 得考虑算法和数据结构优化的)每个对象是不是碰到英雄了,并且ontheFloor这个flag也很重要。 onIt(hx,hy,fx,fy)函数是我刚开始封装的,判断英雄有没有站在地板上。 3,如果在地板上了,调用heroup函数,控制英雄与地板同步上升。之后开始判断是哪个地板。如果relative==10(带 刺木板),那么英雄life-1,并且闪烁三次,并且再向下移动10px才行。如果是type==2(会消失的地板),那 么英雄站上去之后,每隔300ms此地板换一个样式,三次之后木板消失,英雄继续下落。如果是type==3(水平传 送带),那么…… 4,如果英雄脚下没有地板了,那么停止heroup函数,执行herodown。 js 第九步 添加生命条、分数、声音显示//分数显示 var score_pic=new Array('css/img/0.png','css/img/1.png','css/img/2.png','css/img/3.png','css/img/4.png','css/img/5.png','css/img/6.png','css/img/7.png','css/img/8.png','css/img/9.png',); var score=0; function displayScore(){ var s=score; var score0=s%10; var score1=parseInt(s/10)%10; var score2=parseInt(s/100)%10; var score3=parseInt(s/1000); $(".info_score_num img").eq(3).attr('src',score_pic[score0]); $(".info_score_num img").eq(2).attr('src',score_pic[score1]); $(".info_score_num img").eq(1).attr('src',score_pic[score2]); $(".info_score_num img").eq(0).attr('src',score_pic[score3]); setTimeout('displayScore()',50); } //生命条显示 var deadflag=false; function displayLife(){ isdie(); $("#info_life").css('width',nowHP*8+'px'); if(nowHP<=4&&nowHP>0) $("#info_life").css('background','red'); if(nowHP==0&&deadflag==false) {playMusic(1);deadflag=true;} setTimeout('displayLife()',50); } function playMusic(i) { var player = $("#player")[0]; if(i==1) { player.src='css/sound/dead.mp3'; player.play(); } } 分数显示:我的分数是用图片显示的。就是获取score后,得到个、十、百、千位的值,然后对应我的数字(艺术字样式)的图片显示出来。和之前英雄的显示类似。生命条显示:我把生命分成12份,每少1,生命条的宽度小8.3%,当nowHP小于4,设置生命条颜色为红色。声音:当nowHP为0,执行声音函数,播放死亡的音效。js 第十步 排行榜排行榜,我有三种方法去实现。但是我这个游戏也不是多人玩的,不想做排行榜。方法介绍给大家:1,运用php+mysql。用php写好之后,把获取的值转递给页面,页面来显示。但是,php文件必须在xampp目录下才可以运行,意味着,我要把html、js文件都移过去。麻烦。2,运用txt文本。大家学c的时候,一定操作过用c来对txt流进行读和写吧。就是这种思想。第一,每次游戏结束后,你要对玩家的姓名和分数进行写入。第二,每次打开排行榜,你要读取txt文本,获取值。因为txt文本的特性,所以读取之后你要进行数据提取稍微麻烦一点点(相信大家领会我的意思)。3,用js/json文件,存数据。但是好像只能读不能写。进行下一个游戏的开发!飞机大战h5小游戏开发指尖大冒险、跳一跳升级版html5游戏全代码总结虽然引用了一些框架,但是都是浅尝辄止。而且对英雄和地板的判断那一块,都是靠for循环,和时间函数来暴力执行(一秒2000次,我都害怕)。所以多看点算法,数据结构,框架还是很重要的,可以进行代码的优化,使其效率更高、更准确。继承也是一个很重要的地方。比如地板,我本可以定义一个父类,然后对每种类型的地板进行继承父类,然后重写函数,不但节约代码,效率也高。但是我看了找js的书,也没有找到一种好的办法来继承(因为floor里面还有一个Image对象,我其实操作的是Image对象,但是Image对象是不可以编写定义的),所以只好把所有的地板都放在一个类中,用不同的函数名来区分。
2023年02月