项目整理 将一些曾经用过的功能整合进一个spring-boot
一
由于本人的码云太多太乱了,于是决定一个一个的整合到一个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 */ @Override public 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) */ @Override public 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 */ @Override public 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> { @Override public String doEqualSharding(Collection<String> availableTargetNames, ShardingValue<Integer> shardingValue) { for (String name : availableTargetNames) { /*分成两个库*/ if (name.endsWith(shardingValue.getValue() % 2 + "")) { return name; } } throw new IllegalArgumentException(); } @Override public 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; } @Override public 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=11 preparedStatement.setInt(1, 11); // 再根据分表规则去了t_order_0,t_order_item_0 // o.order_id=1000 preparedStatement.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 generatorConfiguration
PUBLIC "-//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> <!--<!–把xml放入编译环境内 eclipse配置–>--> <!--<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); @Autowired private RestTemplate restTemplate; @Autowired private 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了,可以在按钮上绑定事件直接调用。 // 因此,微信游戏由于大多需要用户先触发获取分值,此处请不要填写如下所示的分享API wx.onMenuShareAppMessage({ //例如分享到朋友圈的API title: '分享标题', // 分享标题 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方法添加我们自定义的认证方式。 */ @Bean UserDetailsService customUserService() { return new CustomUserService(); } @Override protected 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") */ @Override protected 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 @GeneratedValue private 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 @GeneratedValue private Long id; private Long name; @ManyToOne private 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的群友帮我确认我的代码是正确的....