由于本人的码云太多太乱了,于是决定一个一个的整合到一个springboot项目里面。

附上自己的项目地址https://github.com/247292980/spring-boot

功能

1.spring-boot

2.FusionChart

3.thymeleaf

4.vue

5.ShardingJdbc

6.mybatis-generator

7.微信分享授权

8.drools

9.spring-security

10.spring-jpa

11.webjars

12.Aspect

13.drools-drt模板动态生成规则 https://www.cnblogs.com/ydymz/p/9590245.html

14.rabbitmq https://www.cnblogs.com/ydymz/p/9617905.html

15.zookeeper https://www.cnblogs.com/ydymz/p/9626653.html

16.mongodb https://www.cnblogs.com/ydymz/p/9814875.html

17.mysql的存储过程 https://www.cnblogs.com/ydymz/p/9828707.html

18.前端懒加载 https://www.cnblogs.com/ydymz/p/9829150.html

19.netty https://www.cnblogs.com/ydymz/p/9849879.html

20.postgresql https://www.cnblogs.com/ydymz/p/9858795.html

21.树的遍历 https://www.cnblogs.com/ydymz/p/10076891.html

二 spring-boot

第一个就是springboot的helloworld了,具体不说什么,就是快捷开发。

写这个的速度限制是我电脑加载的速度!!

三 FusionCharts

FusionCharts.js 是一个很老的图表插件。老到在我们要使用的时候,不仅要导入js代码,还要导入你要的对应swf模板文件,导完了还要你按他们的规矩写相应的数据格式,简直是反程序员啊。

1.目录

2.代码

这是用xml导入数据的格式

<!DOCTYPE html>
<html>
<head><meta name="viewport" content="width=device-width"><title>3D双柱图xml</title><script type="text/javascript" src="/Charts/jquery-1.7.2.js"></script><script type="text/javascript" src="/Charts/FusionCharts.js"></script><script type="text/javascript">$(function () {FusionCharts.debugMode.enabled(true);FusionCharts.debugMode.outputTo(function () {console.log(arguments);});var myChart = new FusionCharts("../Charts/MSColumn3D.swf", "54356345", "100%", "520", "0");myChart.setXMLUrl("doubleColumn3D.xml");// myChart.setXMLUrl("/data/doubleColumn3D.xml");myChart.render("chart");console.log(myChart);});</script>
</head>
<body>
<div id="chart"></div>
</body>
</html>

这是用json导入数据

<!DOCTYPE html>
<html>
<head><meta name="viewport" content="width=device-width"><title>3D双柱图json</title><script type="text/javascript" src="/Charts/jquery-1.7.2.js"></script><script type="text/javascript" src="/Charts/FusionCharts.js"></script>
</head>
<body>
<script type="text/javascript">$(function () {var column3D = new FusionCharts("/Charts/MSColumn3D.swf", "myChartId", "100%", "520", "0");var jsonData = {"chart": {"caption": "Sales in Last Two Years","subcaption": "Quarter-wise comparison","xaxisname": "Quarter","yaxisname": "Sales (In USD)","palette": "2","numberprefix": "$","yaxismaxvalue": "32000","numdivlines": "3",},"categories": [{"category": [{"label": "Q1"},{"label": "Q2"},{"label": "Q3"},{"label": "Q4"}]}],"dataset": [{"seriesname": "Previous Year","data": [{"value": "10000"},{"value": "11500"},{"value": "12500"},{"value": "15000"}]},{"seriesname": "Current Year","data": [{"value": "25400"},{"value": "29800"},{"value": "21800"},{"value": "26800"}]}]};column3D.setJSONData(jsonData);column3D.render("doubleColumn3DChart");console.log(column3D);});
</script>
<div id="doubleColumn3DChart"></div>
</body>
</html>

3.注意

swf,js父目录一定是Charts。
xml父目录一定是data。
浏览器一定要装flash player。
就算装了flash player浏览器,现在都很良心的默认禁止,必须要网页申请权限 或者 浏览器自己打开。

当年写的时候,还没出现后两个,倒是现在重现了这技术的时候,才发现这bug,感觉FusionCharts应该过时了。

毕竟大数据这么久了,相应的数据显示已经很智能,像FusionCharts这种简直能放弃就放弃吧。

四 thymeleaf

thymeleaf其实在之前的几节也有用上thymeleaf了。

但是,我个人是坚定的前后分离的拥护者,可惜工作基本都是往全栈工程师培养的。

公司的测试用的jenkins等测试工具是后端搭的,测试文档还是我们写的,也就是基本测不出什么bug的...

前端基本上只负责css的编写和html,数据填充和ajax都要我们自己填...

安卓和ios的同事也是大爷,9点钟反映问题,下午才回复的,甩锅一个比一个块,态度一个比一个端正...

至于th标签,用是可以用的。但是,不建议掌握也不打算讲。

我的建议是后端开发与其熟悉一钟限制性特高的thymeleaf的标签,还不如老老实实的不断使用h5原生代码,jq,vue这三个知识点,然后根据这三个猜测其他前端框架怎么用...(这里吐槽一下,公司前端不会vue...)

1.文件

pom.xml

<dependencies><!--boot启动,其实后面有-starter的包,通常spring都写好了一些默认配置-->   <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><!--web启动,默认用tomcat端口8080,若不导入这个包,将是普通的boot启动,死活启动不了--> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--thymeleaf包--> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!--web包--> <dependency><groupId>org.springframework</groupId><artifactId>spring-web</artifactId><version>5.0.5.RELEASE</version></dependency></dependencies>

application.properties

########################################################
###THYMELEAF (ThymeleafAutoConfiguration)
########################################################
#spring.thymeleaf.prefix=classpath:/templates/
#spring.thymeleaf.suffix=.html
#spring.thymeleaf.mode=HTML5
#spring.thymeleaf.encoding=UTF-8
## ;charset=<encoding> is added
#spring.thymeleaf.content-type=text/html
#set to false for hot refresh#理论上已经不需要以上的配置了,只要设置thymeleaf的缓存不保存即可
spring.thymeleaf.cache=false

2.注意

额,这玩意在我看起来,搭建速度上限也是电脑响应速度的....

但是,初学者还是有一些搭建失败的情况。

我就总结一下

导入thymeleaf却启动不了或者启动的不是tomcat,没导入spring-boot-starter-xxx的相关包。

不了解thymeleaf的默认配置,可以看下application.properties,spring.thymeleaf.cache=false这个配置在开发要false,正式要true,js的变更应用版本号控制。

根据配置明显html文件应该放在resourse文件夹下的templates文件夹里面(idea的情况,eclipse的话不清楚classpath是什么文件夹,不过一样是classpath下的templates文件夹

springMVC跳转的时候不用写后缀,这里和跳到jsp有很大不同,刚刚从jsp来thymeleaf的十有八九犯这个错误,至于为什么配置文件里面注释掉的部分有写。

vue

1.

当我第一次碰到vue,理解了mvvc之后,曾经觉得这是一个很好的东西,方便前端方便后端。

但是随着工作时间加长,我突然醒悟我一个后端被忽悠去学了一个前端的玩意就不说了,我所在的公司就没一个前端使用过vue。(现公司1w+员工,不知道算不算大公司)

所以我对这玩意其实是持有很大的偏见的。

吹得高大上,但是前端不用。

说是前端框架,很有可能是忽悠人去当全栈。

2.代码

<!DOCTYPE html>
<head><meta charset="UTF-8"><title>Title</title><script src="/js/vue.min.js"></script>
</head>
<style>.class1 {background: #444;color: #eee;}
</style>
<body>
<div id="app"><p>{{ message }}</p><input v-model="message"/><br><br><div v-html="htmlMsg"></div><br><br><label for="ck1">修改颜色</label><input id="ck1" type="checkbox" v-model="isClass1"><div v-bind:class="{class1: isClass1}">directiva v-bind:class</div><!--双向绑定--><!--vue的data绑定的数据,被其全局代理,即r1值变为true时,isClass1也等于true--><br><br><!--<button v-on:click="counter += 1">点一下</button>--><button v-on:click="count">点一下</button><p>这个按钮被点击了 {{ counter }} 次。</p><br><br>
</div>
<!-- vue的JavaScript 代码需要放在尾部(指定的HTML元素之后) -->
<script src="/js/mvvc.js"></script>
</body>
</html>

new Vue({el: '#app',data: {message: 'mvvc hi!',htmlMsg: '<h1>hi!</h1>',isClass1: false,counter: 0},methods: {count: function (event) {// `this` 在方法里指当前 Vue 实例alert('count 1!');this.counter += 1;// `event` 是原生 DOM 事件if (event) {alert(event.target.tagName);}}}
})

3.部分功能

v-if    某元素是否显示

v-model  修改元素的值

v-bind   修改元素的属性,style

v-html   修改html元素

v-on    绑定方法

基本上这五个是非常的常用了,v-bind或许是里面最小用到的,写上他的主要原因就是知道这五个就能完成大多数骚操作了。

另外,他还有各种缩写之类的,虽然看起来很方便。

但是缩写的格式不统一,不像标准的v-xx那样让人一看就知道是什么包出来的,混合使用时强迫症简直受不了,就像一地人参里面有几颗土豆。

所以我是不建议使用缩写,可读性太差。

4.注意

使用vue.js的时候,new Vue必须放到html页面的下面,必须在所有html元素渲染之后,才能生效。

由此引入了mvvc的概念的话,业务代码必须在所有元素渲染之后才生效。那么,静态资源放在body前,业务的js代码放在最后面。

当然,强迫症如我就是css放在body上面,js放在下面,js中的业务代码在最下面。

还有一些v-for的,但是我实际用的时候还是用js的for循环生成好html文件,然后jq插进去,简单粗暴。。。

当然,要是不只是展示数据,还有操作数据的话,那么v-for里面用上v-model是非常好的。(也就各种后台系统有这种需求了)

五 ShardingJdbc

1.

分库分表这东西以前实现,一直是操作数据前,根据id来判断的。直到遇到ShardingJdbc这个第三方包,真的是代码简单易懂的代表啊!

2.代码

ModuloTableShardingAlgorithm.java
public class ModuloTableShardingAlgorithm implements SingleKeyTableShardingAlgorithm<Integer> {/*** select * from t_order from t_order where order_id = 11* └── SELECT *  FROM t_order_1 WHERE order_id = 11* select * from t_order from t_order where order_id = 44* └── SELECT *  FROM t_order_0 WHERE order_id = 44*/@Overridepublic String doEqualSharding(Collection<String> availableTargetNames, ShardingValue<Integer> shardingValue) {for (String each : availableTargetNames) {if (each.endsWith(shardingValue.getValue() % 2 + "")) {return each;}}throw new IllegalArgumentException();}/*** select * from t_order from t_order where order_id in (11,44)* ├── SELECT *  FROM t_order_0 WHERE order_id IN (11,44)* └── SELECT *  FROM t_order_1 WHERE order_id IN (11,44)* select * from t_order from t_order where order_id in (11,13,15)* └── SELECT *  FROM t_order_1 WHERE order_id IN (11,13,15)* select * from t_order from t_order where order_id in (22,24,26)* └──SELECT *  FROM t_order_0 WHERE order_id IN (22,24,26)*/@Overridepublic Collection<String> doInSharding(Collection<String> availableTargetNames, ShardingValue<Integer> shardingValue) {Collection<String> result = new LinkedHashSet<String>(availableTargetNames.size());for (Integer value : shardingValue.getValues()) {for (String tableName : availableTargetNames) {if (tableName.endsWith(value % 2 + "")) {result.add(tableName);}}}return result;}/*** select * from t_order from t_order where order_id between 10 and 20* ├── SELECT *  FROM t_order_0 WHERE order_id BETWEEN 10 AND 20* └── SELECT *  FROM t_order_1 WHERE order_id BETWEEN 10 AND 20*/@Overridepublic Collection<String> doBetweenSharding(Collection<String> availableTargetNames, ShardingValue<Integer> shardingValue) {Collection<String> result = new LinkedHashSet<String>(availableTargetNames.size());Range<Integer> range = (Range<Integer>) shardingValue.getValueRange();for (Integer i = range.lowerEndpoint(); i <= range.upperEndpoint(); i++) {for (String tableName : availableTargetNames) {if (tableName.endsWith(i % 2 + "")) {result.add(tableName);}}}return result;}
}

ModuloDataBaseShardingAlgorithm.java
public class ModuloDataBaseShardingAlgorithm implements SingleKeyDatabaseShardingAlgorithm<Integer> {@Overridepublic String doEqualSharding(Collection<String> availableTargetNames, ShardingValue<Integer> shardingValue) {for (String name : availableTargetNames) {/*分成两个库*/if (name.endsWith(shardingValue.getValue() % 2 + "")) {return name;}}throw new IllegalArgumentException();}@Overridepublic Collection<String> doInSharding(Collection<String> availableTargetNames, ShardingValue<Integer> shardingValue) {Collection<String> result = new LinkedHashSet<String>(availableTargetNames.size());for (Integer value : shardingValue.getValues()) {for (String name : availableTargetNames) {if (name.endsWith(value % 2 + "")) {result.add(name);}}}return result;}@Overridepublic Collection<String> doBetweenSharding(Collection<String> availableTargetNames, ShardingValue<Integer> shardingValue) {Collection<String> result = new LinkedHashSet<String>(availableTargetNames.size());Range<Integer> range = (Range<Integer>) shardingValue.getValueRange();for (Integer i = range.lowerEndpoint(); i <= range.upperEndpoint(); i++) {for (String name : availableTargetNames) {if (name.endsWith(i % 2 + "")) {result.add(name);}}}return result;}
}

ShardingJdbc.java
public class ShardingJdbc {/*** main方法*/public static void main(String[] args) {Map<String, DataSource> dataSourceMap = new HashMap<String, DataSource>(2);dataSourceMap.put("sharding_0", createDataSource("sharding_0"));dataSourceMap.put("sharding_1", createDataSource("sharding_1"));DataSourceRule dataSourceRule = new DataSourceRule(dataSourceMap);//分表分库的表,第一个参数是逻辑表名,第二个是实际表名,第三个是实际库TableRule orderTableRule = new TableRule("t_order", Arrays.asList("t_order_0", "t_order_1"), dataSourceRule);TableRule orderItemTableRule = new TableRule("t_order_item", Arrays.asList("t_order_item_0", "t_order_item_1"), dataSourceRule);/*** DatabaseShardingStrategy 分库策略* 参数一:根据哪个字段分库* 参数二:分库路由函数** TableShardingStrategy 分表策略* 参数一:根据哪个字段分表* 参数二:分表路由函数** user_id选择哪个库* order_id选择那个表** ModuloDataBaseShardingAlgorithm* ModuloTableShardingAlgorithm* 被2整除是0,反之是1**/ShardingRule shardingRule = new ShardingRule(dataSourceRule, Arrays.asList(orderTableRule, orderItemTableRule), Arrays.asList(new BindingTableRule(Arrays.asList(orderTableRule, orderItemTableRule))), new DatabaseShardingStrategy("user_id", new ModuloDataBaseShardingAlgorithm()), new TableShardingStrategy("order_id", new ModuloTableShardingAlgorithm()));DataSource dataSource = new ShardingDataSource(shardingRule);String sql ="SELECT i.* FROM t_order o JOIN t_order_item i " +"ON o.order_id=i.order_id " +"WHERE o.user_id= ? AND o.order_id = ?";try {Connection connection = dataSource.getConnection();PreparedStatement preparedStatement = connection.prepareStatement(sql);//            preparedStatement.setInt(1, 10);
//            preparedStatement.setInt(2, 1001);//             先根据分库规则去了sharding_1
//             o.user_id=11preparedStatement.setInt(1, 11);
//            再根据分表规则去了t_order_0,t_order_item_0
//            o.order_id=1000preparedStatement.setInt(2, 1000);ResultSet result = preparedStatement.executeQuery();while (result.next()) {System.out.println("1--------" + result.getInt(1));System.out.println("2--------" + result.getInt(2));System.out.println("3--------" + result.getInt(3));}} catch (SQLException e) {e.printStackTrace();}}/*** @param dataSourceName* @return dataSource* @DESCRIPTION 创建数据源*/private static DataSource createDataSource(String dataSourceName) {BasicDataSource dataSource = new BasicDataSource();dataSource.setDriverClassName("com.mysql.jdbc.Driver");dataSource.setUrl(String.format("jdbc:mysql://localhost:3306/%s", dataSourceName));dataSource.setUsername("root");dataSource.setPassword("123456789");return dataSource;}
}

sql语句

#实验数据
CREATE TABLE IF NOT EXISTS t_order_0 (
order_id INT NOT NULL,
user_id INT NOT NULL,
PRIMARY KEY (order_id)
);CREATE TABLE IF NOT EXISTS t_order_item_0 (
item_id INT NOT NULL,
order_id INT NOT NULL,
user_id INT NOT NULL,
PRIMARY KEY (item_id)
);CREATE TABLE IF NOT EXISTS t_order_1 (
order_id INT NOT NULL,
user_id INT NOT NULL,
PRIMARY KEY (order_id)
);CREATE TABLE IF NOT EXISTS t_order_item_1 (
item_id INT NOT NULL,
order_id INT NOT NULL,
user_id INT NOT NULL,
PRIMARY KEY (item_id)
);
INSERT INTO t_order_1 VALUES ('1001', '10');
INSERT INTO t_order_item_0 VALUES ('0', '1001', '10');
INSERT INTO t_order_item_0 VALUES ('1', '1000', '11');
INSERT INTO t_order_item_0 VALUES ('2', '1001', '10');
INSERT INTO t_order_0 VALUES ('1000', '11');
INSERT INTO t_order_item_1 VALUES ('0', '1000', '11');
INSERT INTO t_order_item_1 VALUES ('1', '1000', '11');

3.注意

没什么好说的代码注释里面已经说得很清楚了。

七 mybatis-generator

没什么好说的,根据数据库的数据结构反向生成mybatis的代码。具体注意点,代码注释也说的很清楚了。

1.代码

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfigurationPUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN""http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd"><generatorConfiguration><!--mysql 连接数据库jar 这里选择自己本地位置--><!--理论上只要不改idea默认maven的设置,基本不会变,但我改了--><!--<classPathEntry location="D:/GitProjects/mybatis-generator/src/main/resources/mysql-connector-java-5.1.44.jar" />--><context id="testTables" targetRuntime="MyBatis3"><commentGenerator><!-- 是否去除自动生成的注释 true:是 : false:否 --><property name="suppressAllComments" value="true"/></commentGenerator><!--数据库连接的信息:驱动类、连接地址、用户名、密码 --><jdbcConnection driverClass="com.mysql.jdbc.Driver"connectionURL="jdbc:mysql://127.0.0.1:3306/webmanager?characterEncoding=UTF-8"userId="root" password="123456789"></jdbcConnection><!-- 默认false,把JDBC DECIMAL 和 NUMERIC 类型解析为 Integer,为 true时把JDBC DECIMAL 和NUMERIC 类型解析为java.math.BigDecimal --><javaTypeResolver><property name="forceBigDecimals" value="false"/></javaTypeResolver><!-- targetProject:生成PO类的位置 targetProject的文件夹必须存在 targetPackage就不一定--><javaModelGenerator targetPackage="com.lgp.domain" targetProject="src/main/java"><!-- enableSubPackages:是否让schema作为包的后缀 --><property name="enableSubPackages" value="false"/><!-- 从数据库返回的值被清理前后的空格 --><property name="trimStrings" value="true"/></javaModelGenerator><!-- targetProject:mapper映射文件生成的位置如果maven工程只是单独的一个工程,targetProject="src/main/java"若果maven工程是分模块的工程,targetProject="所属模块的名称",例如:targetProject="ecps-manager-mapper",下同--><sqlMapGenerator targetPackage="mapper" targetProject="src/main/resources/static"><!-- enableSubPackages:是否让schema作为包的后缀 --><property name="enableSubPackages" value="false"/></sqlMapGenerator><!-- targetPackage:mapper接口生成的位置 --><javaClientGenerator type="XMLMAPPER" targetPackage="com.lgp.mapper" targetProject="src/main/java"><!-- enableSubPackages:是否让schema作为包的后缀 --><property name="enableSubPackages" value="false"/></javaClientGenerator><!-- 指定数据库表 --><!--<table schema="" tableName="one"></table>--><!--但是还是通杀好!--><table schema="" tableName="%"></table></context>
</generatorConfiguration>

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.lgp</groupId><artifactId>mybatis-generator</artifactId><version>0.0.1-SNAPSHOT</version><packaging>jar</packaging><name>mybatis-generator</name><description>Demo project for Spring Boot</description><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.0.4.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.mybatis.generator</groupId><artifactId>mybatis-generator-core</artifactId><version>1.3.0</version></dependency></dependencies><build><!--&lt;!&ndash;把xml放入编译环境内 eclipse配置&ndash;&gt;--><!--<resources>--><!--<resource>--><!--<directory>src/main/java</directory>--><!--<includes>--><!--<include>**/*.xml</include>--><!--</includes>--><!--<filtering>true</filtering>--><!--</resource>--><!--</resources>--><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin><plugin><groupId>org.mybatis.generator</groupId><artifactId>mybatis-generator-maven-plugin</artifactId><version>1.3.2</version><!--配置要用的驱动--><dependencies><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.44</version></dependency></dependencies><configuration><!--配置文件的路径--><configurationFile>src/main/resources/generatorConfig.xml</configurationFile><overwrite>true</overwrite></configuration></plugin></plugins></build></project>

八 微信分享授权

1.

关于微信分享和授权,我是觉得代码是不重要的,主要是思路。(主要是没有企业级公众号,而且也不能暴露公司的出来)

为什么呢?

因为微信的坑爹逻辑能看代码看出来的话,只能说是天生的程序员。

微信通用的坑就是一定是https!

但是你用http还是在自己的机子上能搞出自己想要的结果,但是一对外就呵呵了。

所以,为了排除这种情况,你必须使用微信的开发者工具!

2.分享

可以像我代码那样什么都不写,这样的话,还是能在电脑微信那里发出正确的分享链接出来的,23333

所以记得用微信开发者工具!

还要注意的是,微信公众号要开分享的功能,而且微信开发者工具开发必须拉近公众号开发人员里面。

逻辑来说,只要对着微信那个api填基本不会出错。

3.授权

授权就要细说了。

去微信的授权网站-跳到公司的授权服务器-获取到code

正常人是这样理解的吧,但是是错的!

因为获取的code是拼接在url里面,公司的授权服务器还要加个重定向页面的参数,你才能在该页面获得code。

这个方法应该是通用的,不应该每一次分享的地方落地页都一样。

获得code

微信的授权网站-跳到公司的授权服务器-跳到重定向参数指向的网址-获取到code

获取了code之后,要根据code获取access_token

这个就简单了,直接走微信的api就好,还附带各种信息

注意的是

这个东西有个时限,7200s,两小时。

可以的话直接用刚刚获得的信息里面的refresh_token,刷新时限至30天。

方法也是直接走微信api就可以了。

4.代码

建议看一下,或者直接去码云拷贝源码,或者直接不看...

package com.lgp.wechatshare.handle;import com.fasterxml.jackson.databind.ObjectMapper;
import com.lgp.wechatshare.constant.WeiXinInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;import java.util.HashMap;
import java.util.Map;/*** @AUTHOR lgp* @DATE 2018/4/16 21:27* @DESCRIPTION**/
@Component
public class WeiXinHandler {private static Logger logger = LoggerFactory.getLogger(WeiXinHandler.class);@Autowiredprivate RestTemplate restTemplate;@Autowiredprivate WeiXinInfo weiXinInfo;/*** 生成用于获取access_token的Code的Url** @param redirectUrl* @return*/public String getRequestCodeUrl(String redirectUrl, String scope) {return String.format("https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=%s&state=%s#wechat_redirect",weiXinInfo.getAppID(), redirectUrl, scope, "code");}/*** 获取请求用户信息的access_token** @param code* @return { "* access_token":"ACCESS_TOKEN",* "expires_in":7200,* "refresh_token":"REFRESH_TOKEN",* "openid":"OPENID",* "scope":"SCOPE"* }*/public Map<String, String> getWeiXinAccessInfo(String code) throws Exception {String url = String.format("https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code",weiXinInfo.getAppID(), weiXinInfo.getAppSecret(), code);logger.info("WeiXinClient.getWeiXinAccessInfo.url: {}", url);try {String result = restTemplate.getForObject(url, String.class);logger.info("WeiXinClient.getWeiXinAccessInfo.result: {}", result);ObjectMapper mapper = new ObjectMapper();return mapper.readValue(result, Map.class);} catch (NullPointerException e) {throw new NullPointerException("code 已被使用" + code);} catch (Exception e) {throw new Exception(e.getMessage());}}public String getWeiXinOpenId(String code) throws Exception {Map<String, String> data = getWeiXinAccessInfo(code);return data.get("openid");}public String getAccessToken(String code) throws Exception {Map<String, String> data = getWeiXinAccessInfo(code);return data.get("access_token");}/*** 获取用户信息* grantType 默认为refresh_token* {* "access_token":"ACCESS_TOKEN",* "expires_in":7200,* "refresh_token":"REFRESH_TOKEN",* "openid":"OPENID",* "scope":"SCOPE"* }* 返回的新token于旧token不同*/public Map<String, String> refreshAcessToken(String accessToken, String openId, String grantType) throws Exception {String url = "ttps://api.weixin.qq.com/sns/oauth2/refresh_token?appid==" + openId + "&grant_type=" + grantType + "&refresh_token=" + accessToken;logger.info("WeiXinClient.refreshAcessToken.url: {}", url);try {String result = restTemplate.getForObject(url, String.class);logger.info("WeiXinClient.refreshAcessToken.result: {}", result);ObjectMapper mapper = new ObjectMapper();return mapper.readValue(result, Map.class);} catch (NullPointerException e) {throw new NullPointerException("accessToken和openId已经过期");} catch (Exception e) {throw new Exception(e.getMessage());}}/*** 获取用户信息* {* "openid":" OPENID",* " nickname": NICKNAME,* "sex":"1",* "province":"PROVINCE"* "city":"CITY",* "country":"COUNTRY",* "headimgurl":    "http://thirdwx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/46",* "privilege":[ "PRIVILEGE1" "PRIVILEGE2"     ],* "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"* }*/public Map<String, String> getUserInfo(String accessToken, String openId) throws Exception {String url = "https://api.weixin.qq.com/sns/userinfo?access_token=" + accessToken + "&openid=" + openId + "&lang=zh_CN";logger.info("WeiXinClient.getUserInfo.url: {}", url);try {String result = restTemplate.getForObject(url, String.class);logger.info("WeiXinClient.getUserInfo.result: {}", result);ObjectMapper mapper = new ObjectMapper();return mapper.readValue(result, Map.class);} catch (NullPointerException e) {throw new NullPointerException("accessToken和openId已经过期");} catch (Exception e) {throw new Exception(e.getMessage());}}/*** 检验授权凭证*/public Boolean auth(String accessToken, String openId) throws Exception {Map<String, String> data = new HashMap();String url = "https://api.weixin.qq.com/sns/auth?access_token=" + accessToken + "&openid=" + openId;logger.info("WeiXinClient.auth.url: {}", url);try {String result = restTemplate.getForObject(url, String.class);logger.info("WeiXinClient.auth.result: {}", result);ObjectMapper mapper = new ObjectMapper();data = mapper.readValue(result, Map.class);if ("ok".equals(data.get("errmsg"))) {return true;}return false;} catch (NullPointerException e) {throw new NullPointerException("accessToken和openId已经过期");} catch (Exception e) {throw new Exception(e.getMessage());}}
}

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>分享</title>
</head>
<body>
<p>share to your friends</p>
</body>
<script src="http://res.wx.qq.com/open/js/jweixin-1.2.0.js"></script>
<script>wx.config({debug: false, //调式模式,设置为ture后会直接在网页上弹出调试信息,用于排查问题appId: '',timestamp:"",nonceStr: '',signature: '',jsApiList: [ //需要使用的网页服务接口
//                'checkJsApi', //判断当前客户端版本是否支持指定JS接口
//                'onMenuShareTimeline', //分享给朋友圈'onMenuShareAppMessage', //分享到好友
//                'onMenuShareQQ', //分享到QQ
//                'onMenuShareWeibo',//分享到微博 ']});wx.ready(function () {//ready函数用于调用API,如果你的网页在加载后就需要自定义分享和回调功能,需要在此调用分享函数。// 如果是微信游戏结束后,需要点击按钮触发得到分值后分享,这里就不需要调用API了,可以在按钮上绑定事件直接调用。// 因此,微信游戏由于大多需要用户先触发获取分值,此处请不要填写如下所示的分享APIwx.onMenuShareAppMessage({//例如分享到朋友圈的APItitle: '分享标题', // 分享标题desc: '分享描述', // 分享描述link: 'www.baidu.com', // 分享链接imgUrl: '', // 分享图标success: function () {// 用户确认分享后执行的回调函数console.log("share sucess");},cancel: function () { // 用户取消分享后执行的回调函数}});});wx.error(function (res) {alert(res.errMsg); //打印错误消息。及把 debug:false,设置为debug:ture就可以直接在网页上看到弹出的错误提示});
</script>
<br/>
</html>

九 drools

1

drools是一个规则引擎,是用来做人工智能的。看起来是不是功能很强大?是不是很想学?

在这里我就不指条歪路给你了,不要试图从百度学习drools,直接从drools的项目里面学。

因为drools第一批使用的人没有什么开源精神,他的教程什么的都是要付钱的,付钱的就算了,他们的教程还做不到真正的与时俱进。

因为他们公司做不到与时俱进的使用drools,越新的功能,他的代码充满了不切实际的味道。

充满了落后的设计,落后的知识点,甚至会说新人就先从低版本学起,他好收两次钱。

但是,当程序员没多久的还是交一下这个智商税吧!

因为我推荐的方法不适合大多数人。

直接看官方例子,痛并快乐的啃吧!

https://github.com/kiegroup/drools/tree/master/drools-examples

2.代码

这个我建议各位直接去我码云那里看,因为知识点太散了。

https://gitee.com/a247292980/springBoot/tree/master/drools

而且我的demo里面也没有业务上drools实际应用的代码,我只是搭了个基本环境和几种数据导入到drl文件的方法。

十 spring-security

1.个人看法

spring-security依然是一个很好学习的框架(只要你会看源码,或者官方的例子 https://springcloud.cc/spring-security-zhcn.html 进入这个页面 ctrl+f 样品和指南)

但是,公司单点登录的框架还是选择了自己写网关的控制,2333(我也觉得没做错毕竟涉及到用户信息,但是个人对spring-security还是很有好感的)

了解spring-security最大的收获是让你做单点登录等权限控制功能的思路得到很大的扩展。

2.例子讲解

这次我没有使用mybatis而是用了spring-data-jpa,然后就能不用发sql语句了!开头是这么想的,但是后面发现,还是要给些测试数据的insert。

例子中主要实现了网址拦截和权限控制。基本的讲解都在代码注释里。

3.代码

后端网址拦截主要代码

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {/*** 1.首先当我们要自定义Spring Security的时候我们需要继承自WebSecurityConfigurerAdapter来完成,相关配置重写对应 方法即可。* 2.我们在这里注册CustomUserService的Bean,然后通过重写configure方法添加我们自定义的认证方式。*/@BeanUserDetailsService customUserService() {return new CustomUserService();}@Overrideprotected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {authenticationManagerBuilder.userDetailsService(customUserService());}/*** 3.在configure(HttpSecurity http)方法中,我们设置了登录页面,而且登录页面任何人都可以访问,然后设置了登录失败地址,也设置了注销请求,注销请求也是任何人都可以访问的。* 4.permitAll表示该请求任何人都可以访问,.anyRequest().authenticated(),表示其他的请求都必须要有权限认证。* 5.这里我们可以通过匹配器来匹配路径,比如antMatchers方法,假设我要管理员才可以访问admin文件夹下的内容,我可以这样来写:.*          antMatchers("/admin/**").hasRole("ROLE_ADMIN"),也* 可以设置admin文件夹下的文件可以有多个角色来访问,写法如下:*         antMatchers("/admin/**").hasAnyRole("ROLE_ADMIN","ROLE_USER")* 6.可以通过hasIpAddress来指定某一个ip可以访问该资源,假设只允许访问ip为210.210.210.210的请求获取admin下的资源,写法如下.*          antMatchers("/admin/**").hasIpAddress("210.210.210.210")*/@Overrideprotected void configure(HttpSecurity httpSecurity) throws Exception {httpSecurity.authorizeRequests()
//                留意代码顺序
//                首页(不需要登陆就能访问的页面.antMatchers("/index").permitAll().anyRequest().authenticated()
//                需要登陆才能访问的页面.and().formLogin().loginPage("/login").defaultSuccessUrl("/index").failureUrl("/login?error").permitAll().and().logout().logoutUrl("/logout").logoutSuccessUrl("/login").permitAll().and().rememberMe().tokenValiditySeconds(60 * 60 * 24 * 7).key("kkkkkkkk");}
}

前端权限控制主要代码

    <div class="starter-template"><h1 th:text="${msg.title}"></h1><p class="bg-primary" th:text="${msg.content}"></p><div sec:authorize="hasRole('ROLE_ADMIN')"><p class="bg-info" th:text="${msg.extraInfo}"></p></div><div sec:authorize="hasRole('ROLE_USER')"><p class="bg-info">无更多显示信息</p></div><form th:action="@{/logout}" method="post"><input type="submit" class="btn btn-primary" value="注销"/></form></div>

十一 spring-jpa

1.个人看法

spring-jpa其实和mybatis差不多,但是spring-jpa比它更规范,所以一些自定义的复杂的sql语句,mybatis执行的更好。

但是,其实spring-jpa也支持这种想法,归根到底,只能说mybatis已经占领了市场了。

2.例子讲解

抽出最小的配置代码出来,方便理解。

其实我挺喜欢这样的,先搞个hello world框架,剩下的自己折腾。

在我这个代码里面,有我理解多对一,一对多的测试,虽然都已经注释了,有兴趣的自己试试,记得要删库,看新的表的数据结构。

3.代码

@Entity
public class One {@Id@GeneratedValueprivate Long id;private Long name;//    @OneToOne(cascade = CascadeType.ALL)
//    private Two two;//    @OneToMany
//    private Set<Two> two;//    @ManyToMany
//    private Set<Two> two;
}

@Entity
public class Two {@Id@GeneratedValueprivate Long id;private Long name;@ManyToOneprivate One one;
}

十二.webjars

1.个人看法

一个管理静态资源文件的包,额,离前后端分离越来越远了啊。。。。。

2.例子讲解

写了基本配置和正常配制,正常配置就是忽略版本号的那种。

其实他还有个能力,可以给一些index.js这些业务js加版本号index.js?v=1这种,但是这样搞的话就真的离前后端分离越来越远了啊。。。

老实说,老大有点心动。但是,这功能鸡肋啊,因为我们不会吧业务代码deploy到maven仓库里面,所以这功能真的233333

3.代码

/*** Created by IntelliJ IDEA.* User: a247292980* Date: 2017/08/14* <p>* 处理WebJars,无视版本号,使用带版本号的方法可注释此类**/
@Controller
public class WebJarController {private final WebJarAssetLocator assetLocator = new WebJarAssetLocator();/*** RequestMapping的地址是固定的*/@ResponseBody@RequestMapping("/webjarslocator/{webjar}/**")public ResponseEntity locateWebjarAsset(@PathVariable String webjar, HttpServletRequest request) {try {String mvcPrefix = "/webjarslocator/" + webjar + "/";String mvcPath = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);String fullPath = assetLocator.getFullPath(webjar, mvcPath.substring(mvcPrefix.length()));return new ResponseEntity(new ClassPathResource(fullPath), HttpStatus.OK);} catch (Exception e) {return new ResponseEntity(HttpStatus.NOT_FOUND);}}}

<head><!--<script src="/webjars/jquery/3.1.1/jquery.min.js"></script>--><script src="/webjars/jquery/jquery.min.js"></script><!--<script src="/webjars/bootstrap/3.3.7-1/js/bootstrap.min.js"></script>--><script src="/webjars/bootstrap/js/bootstrap.min.js"></script><title>demo.html Demo</title><!--<link rel="stylesheet" href="/webjars/bootstrap/3.3.7-1/css/bootstrap.min.css" />--><link rel="stylesheet" href="/webjars/bootstrap/css/bootstrap.min.css" />
</head>

十三 aspect

1.个人看法

很强的一个功能面向切面编程,看起来能开发成各种各样的工具,不过我喜欢直接开发成日志拦截器,那就少写了很多日志啦。

2.例子讲解

主要有三个,一个是方法切面,一个是注释切面,还有一个是我用的根据注释切面写的日志拦截,基本讲解我卸载代码里面了,强烈建议动手跑一下我写的日志拦截。

3.代码

package com.lgp.aop.aop;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;/*** @AUTHOR lgp* @DATE 2018/8/3 17:12* @DESCRIPTION**/
@Aspect
@Component
public class AnnotationAspect {protected org.slf4j.Logger logger = LoggerFactory.getLogger(this.getClass());/*** @Aspect 作用是把当前类标识为一个切面供容器读取* @Before 标识一个前置增强方法,相当于BeforeAdvice的功能* @AfterReturning 后置增强,相当于AfterReturningAdvice,方法退出时执行* @AfterThrowing 异常抛出增强,相当于ThrowsAdvice* @After final增强,不管是抛出异常或者正常退出都会执行* @Around 环绕增强,相当于MethodInterceptor** 除了@Around外,每个方法里都可以加或者不加参数JoinPoint,* 如果有用JoinPoint的地方就加,不加也可以,JoinPoint里包含了类名、被切面的方法名,参数等属性,可供读取使用。* @Around参数必须为ProceedingJoinPoint pjp.proceed相应于执行被切面的方法。* @AfterReturning方法里, 可以加returning = “XXX”,XXX即为在controller里方法的返回值。* @AfterThrowing方法里, 可以加throwing = "XXX",供读取异常信息,throwing = "ex"*/@Pointcut(value = "@annotation(com.lgp.aop.aop.Log)")public void log() {}@Before("log()")public void deBefore(JoinPoint joinPoint) throws Throwable {System.out.println("annotation before");}@Around("@annotation(log)")public Object around(ProceedingJoinPoint pjp, Log log) {//获取注解里的值System.out.println("annotation around:" + log.description());try {return pjp.proceed();} catch (Throwable throwable) {throwable.printStackTrace();return null;}}
}

package com.lgp.aop.aop;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;/*** Created by IntelliJ IDEA.* User: a247292980* Date: 2017/08/14* <p>* 方法的aop*/
@Aspect
@Component
public class FunctionAspect {protected org.slf4j.Logger logger = LoggerFactory.getLogger(FunctionAspect.class);/*** execution函数* 用于匹配方法执行的连接点,语法为:execution(方法修饰符(可选)  返回类型  方法名  参数  异常模式(可选))* 参数部分允许使用通配符:* *  匹配任意字符,但只能匹配一个元素* .. 匹配任意字符,可以匹配任意多个元素,表示类时,必须和*联合使用* +  必须跟在类名后面,如Horseman+,表示类本身和继承或扩展指定类的所有类* 除了execution(),Spring中还支持其他多个函数,这里列出名称和简单介绍,以方便根据需要进行更详细的查询* @annotation() 表示标注了指定注解的目标类方法* 例如 @annotation(org.springframework.transaction.annotation.Transactional) 表示标注了@Transactional的方法* args()通过目标类方法的参数类型指定切点 例如 args(String) 表示有且仅有一个String型参数的方法* @args() 通过目标类参数的对象类型是否标注了指定注解指定切点 如 @args(org.springframework.stereotype.Service) 表示有且仅有一个标注了@Service的类参数的方法* within()通过类名指定切点 如 with(examples.chap03.Horseman) 表示Horseman的所有方法* @within() 匹配标注了指定注解的类及其所有子类 如 @within(org.springframework.stereotype.Service) 给Horseman加上@Service标注,则Horseman和Elephantman 的所有方法都匹配* target()通过类名指定,同时包含所有子类 如 target(examples.chap03.Horseman)  且Elephantman extends Horseman,则两个类的所有方法都匹配* @target() 所有标注了指定注解的类 如 @target(org.springframework.stereotype.Service) 表示所有标注了@Service的类的所有方法* this() 大部分时候和target()相同,区别是this是在运行时生成代理类后,才判断代理类与指定的对象类型是否匹配* *//*** 定义有一个切入点,范围为service包下的类*/@Pointcut("execution(public * com.lgp.aop.service.*.*(..))")public void service() {}@Around("service()")public void doAround(JoinPoint joinPoint) throws Throwable {// 接收到请求,记录请求内容ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = attributes.getRequest();// 记录下请求内容System.out.println("URL : " + request.getRequestURL().toString());System.out.println("HTTP_METHOD : " + request.getMethod());System.out.println("IP : " + request.getRemoteAddr());System.out.println("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());System.out.println("ARGS : " + Arrays.toString(joinPoint.getArgs()));}}

4.注意

基本无坑,但是我还是傻了一下,因为这玩意用了几次,在抽取出来的时候,竟然忘了导spring-boot配aop的启动配置,pom.xml少了一个依赖,而折腾了半个多小时。

在这里,感谢spring-cloud的群友帮我确认我的代码是正确的....

转载于:https://www.cnblogs.com/ydymz/p/9391653.html

整理代码,将一些曾经用过的功能整合进一个spring-boot相关推荐

  1. redis高并发原理_Java中的42行代码中的URL缩短服务— Java(?!)Spring Boot + Redis...

    redis高并发原理 显然,编写URL缩短服务是新的"世界,您好! "在物联网/微服务/时代的世界中. 一切始于在45行Scala中的URL缩短服务-整洁的Scala,以Spray ...

  2. Java中的42行代码中的URL缩短服务— Java(?!)Spring Boot + Redis

    显然,编写URL缩短服务是新的" Hello,world! "在IoT /微服务/时代的世界中. 一切始于在45行Scala中的URL缩短服务 -整洁的Scala,以Spray和R ...

  3. 松哥整理了 15 道 Spring Boot 高频面试题,看完当面霸

    什么是面霸?就是在面试中,神挡杀神佛挡杀佛,见招拆招,面到面试官自惭形秽自叹不如!松哥希望本文能成为你面霸路上的垫脚石! 做 Java 开发,没有人敢小觑 Spring Boot 的重要性,现在出去面 ...

  4. Spring Boot 面试题整理

    Spring Boot 面试题整理 2018年08月12日 22:32:35 Time_sg 阅读数 19380 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文 ...

  5. 松哥整理了 15 道 Spring Boot 高频面试题,看完当面霸!

    点击"牧码小子"关注,和众多大牛一起成长! 关注后,后台回复 java ,领取松哥为你精心准备的技术干货! 什么是面霸?就是在面试中,神挡杀神佛挡杀佛,见招拆招,面到面试官自惭形秽 ...

  6. Spring Boot项目利用MyBatis Generator进行数据层代码自动生成

    概 述 MyBatis Generator (简称 MBG) 是一个用于 MyBatis和 iBATIS的代码生成器.它可以为 MyBatis的所有版本以及 2.2.0之后的 iBATIS版本自动生成 ...

  7. java多个数据库数据进行访问_通过Spring Boot配置动态数据源访问多个数据库的实现代码...

    之前写过一篇博客<Spring+Mybatis+Mysql搭建分布式数据库访问框架>描述如何通过Spring+Mybatis配置动态数据源访问多个数据库.但是之前的方案有一些限制(原博客中 ...

  8. git上托管的代码如何部署在阿里云上_居然仅用浏览器搞定Spring Boot应用的开发与部署...

    最近有幸试用了一下阿里云的一个新产品:云开发平台,体验一把全新的开发模式!虽然中间也碰到了一些问题,但整体的体验透露着未来感,因为整个过程都不需要使用我们最常用的IDEA,仅依靠浏览器就把一个Spri ...

  9. springboot java获取版本号_深入实践Spring Boot 实战篇,大佬整理出的PDF文档

    如何使用Spring Boot 本文章将会详细介绍如何使用Spring Boot.它覆盖了构建系统,自动配置和运行/部署选项等主题.我们也覆盖了一些Spring Boot最佳实践.尽管Spring B ...

最新文章

  1. 时空穿越!谷歌利用众包老照片还原儿时3D街景,浏览器即可体验
  2. 从5G到6G的思考:需求、挑战、技术趋势
  3. 高效编程所需要做的14件事
  4. 塔式服务器、机架式服务器、刀片服务器区别小结
  5. Android之开发性能优化简介
  6. linux系统平均价格,简单认识Linux系统平均负载
  7. mysql-入门教程
  8. php 魔方加密还原,PHP魔方解密 - osc_80l29rkk的个人空间 - OSCHINA - 中文开源技术交流社区...
  9. 计算机桌面ie图标无法删除,桌面ie图标删除不了的解决方法
  10. 典型周期性电信号的测量
  11. #10019. 「一本通 1.3 例 2」生日蛋糕
  12. 除了摆地摊or送外卖,程序猿如何体面的赚零花钱?
  13. 计算机不接受跨专业考研,2016跨专业考研需谨慎的专业解读:计算机
  14. 【LabVIEW懒人系列教程-小白入门】1.16LabVIEW程序结构之小试身手
  15. java 工作两年的简历_工作经验只有两年的Java开发,简历中需要写学校经历吗?...
  16. zigbee 终端设备如何离开当前网络
  17. 一笔画问题(nyoj 42)
  18. 高质量实时渲染课程笔记(三)— 实时阴影渲染1(Shadow Mapping、PCF、PCSS)
  19. 前端(DOM 、BOM 和 事件 )
  20. 移动开发测试工具——Bugtags的集成

热门文章

  1. IT从业人员必看的10大论坛
  2. 工业电气自动化实习报告
  3. 积分商城平台的几个常见用途!
  4. 多分类问题的precision和recall以及F1 scores的计算
  5. c花体复制_可复制花体英文字母(较全)
  6. Navicat连接MySQL8报错:Client does not suport authentication protocal requested by server
  7. Springboot 项目通过 gitlab CI/CD 集成 k8s 自动部署
  8. C 语言新手入门教程,0 基础的小伙伴请进~(书籍推荐+项目推荐)
  9. 今日作息及食谱(7.1)
  10. Personal ArcSDE、Workgroup ArcSDE、Enterprise ArcSDE介绍