- 1.用户登录业务介绍
- 2.JWT进行跨域身份验证
- 3.用户微服务
- 微信扫码登录
- 微信支付
- 定时任务
- Canal
- spring security
- jenkins
- 服务器部署
- 1.安装nacos
- a.修改nacos/conf目录下的
application.properties - 脚本,修改">b.编辑 vim startup.sh脚本,修改
- 脚本,在源码包有,在 nacos/conf/nacos-mysql.sql,把这个导入到数据中">c.首先创建一个数据库
nacos,然后运行数据库脚本,在源码包有,在 nacos/conf/nacos-mysql.sql,把这个导入到数据中 - d.启动nacos
- e.关闭nacos
- f.将nacos config配置中心的文件导入
- a.修改nacos/conf目录下的
- 2.安装jdk
- 3.打包本地jar,放置服务器运行
- 4.安装redis
- 5.安装mysql
- 6.安装nginx
- 1.安装nacos
1.用户登录业务介绍
1.1 单一服务器模式
早期单一服务器,用户认证。
缺点:单点性能压力,无法扩展

1.2 SSO(single sign on)模式
分布式,SSO(single sign on)模式

单点登录常见的三种方式:
1.session广播机制实现
2.cookie+redis实现
在redis key中生成唯一随机值,在value存放用户数据。cookie中存放redis的key值。
访问项目模块时,发送请求带着cookie进行发送,获取cookie值,到redis进行查询,如果查询到就是已登录,未查询到就重新登录。
3.使用token实现
token是什么?
- 是按照一定规则生成的字符串,字符串可以包含用户信息

2.JWT进行跨域身份验证
传统用户身份验证

Internet服务无法与用户身份验证分开。一般过程如下︰
1.用户向服务器发送用户名和密码。2.验证服务器后,相关数据(如用户角色,登录时间等)将保存在当前会话中。3.服务器向用户返回session_id , session信息都会写入到用户的Cookie。4.用户的每个后续请求都将通过在Cookie中取出session_id传给服务器。5.服务器收到session_id并对比之前保存的数据,确认用户的身份。这种模式最大的问题是,没有分布式架构,无法支持横向扩展。
解决方案
- session广播
- 将透明令牌存入cookie,将用户身份信息写入redis
另外一种灵活的解决方案∶
- 使用自包含令牌,通过客户端保存数据,而服务器不保存会话数据。JWT是这种解决方案的代表。
JWT令牌
访问令牌的类型

JWT生成字符串包含三部分
- jwt头信息
- 有效载荷 包含主题信息(用户信息)
- 签名哈希 防伪标志
JWT的用法
客户端接收服务器返回的JWT,将其存储在Cookie或localStorage中。
此后,客户端将在与服务器交互中都会带JWT。如果将它存储在Cookie中,就可以自动发送,但是不会跨域,因此一般是将它放入HTTP请求的Header Authorization字段中。当跨域时,也可以将WT被放置于POST请求的数据主体中。
1.引入依赖
<!-- JWT --><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version></dependency>
2.创建JWT工具类
import io.jsonwebtoken.Claims;import io.jsonwebtoken.Jws;import io.jsonwebtoken.Jwts;import io.jsonwebtoken.SignatureAlgorithm;import org.apache.commons.lang.StringUtils;import javax.servlet.http.HttpServletRequest;import java.util.Date;public class JwtUtils {/**EXPIRE设置token的过期时间*/public static final long EXPIRE = 1000 * 60 * 60 * 24;/**密钥*/public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO";/**生成token字符串的方法*/public static String getJwtToken(String id, String nickname){String JwtToken = Jwts.builder()/**设置jwt的头信息*/.setHeaderParam("typ", "JWT").setHeaderParam("alg", "HS256")/**分类和过期时间*/.setSubject("hzb").setIssuedAt(new Date()).setExpiration(new Date(System.currentTimeMillis() + EXPIRE))/**设置token的主体信息,存储用户信息*/.claim("id", id).claim("nickname", nickname).signWith(SignatureAlgorithm.HS256, APP_SECRET).compact();return JwtToken;}/*** 判断token是否存在与有效* @param jwtToken* @return*/public static boolean checkToken(String jwtToken) {if(StringUtils.isEmpty(jwtToken)) return false;try {Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);} catch (Exception e) {e.printStackTrace();return false;}return true;}/*** 判断token是否存在与有效* @param request* @return*/public static boolean checkToken(HttpServletRequest request) {try {String jwtToken = request.getHeader("token");if(StringUtils.isEmpty(jwtToken)) return false;Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);} catch (Exception e) {e.printStackTrace();return false;}return true;}/*** 根据token获取会员id* @param request* @return*/public static String getMemberIdByJwtToken(HttpServletRequest request) {String jwtToken = request.getHeader("token");if(StringUtils.isEmpty(jwtToken)) return "";Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);Claims claims = claimsJws.getBody();return (String)claims.get("id");}}
JWT问题和趋势
- WT不仅可用于认证,还可用于信息交换。善用WT有助于减少服务器请求数据库的次数。
- 生产的token可以包含基本信息,比如id、用户昵称、头像等信息,避免再次查库
- 存储在客户端,不占用服务端的内存资源
- JWT默认不加密,但可以加密。生或原始令牌后,可以再次对其进行加密。
- 当JWT未加密时,—些私密数据无法通过JWT传输
- JWT的最大缺点是服务器不保存会话状态,所以在使用期间不可能取消令牌或更改令牌的权限。也就是说,一旦WT签发,在有效期内将会一直有效。
- JWT本身包含认证信息、,token是经过base64编码,所以可以解码,因此token加密前的对象不应该包含敏感信息,一旦信息泄露,任何人都可以获得令牌的所有权限。为了减少盗用,JWT的有效期不宜设置太长。对于某些重要操作,用户在使用时应该每次都进行进行身份验证。
- 为了减少盗用和窃取,JWT不建议使用HTTP协议来传输代码,而是使用加密的HTTPS协议进行传输。
整合JWT令牌
1.在callback中生成jwt
//生成jwtString token = JwtUtils.getJwtToken(member.getId(),member.getNickname());//存入cookie//CookieUtils.setCookie(request,response,"guli-jwt-token",token);//因为端口号不同存在跨域问题,cookie不能跨域,所有使用url重写return "redirect:http://localhost:3000?token=" + token;
2.在前端获取url的token值,去后台获取用户信息
//前端http request 拦截器,request.jsservice.interceptors.request.use(config => {//debugger//判断cookie里面guli_token是否有值if (cookie.get('token')) {//吧获取的token放在头部headers中config.headers['token'] = cookie.get('token');}return config},err => {return Promise.reject(err);})//default.vuecreated() {//获取路径里面token值this.token = this.$route.query.token//console.log(this.token)if(this.token) {//判断路径是否有token值this.wxLogin()}this.showInfo()},methods:{//微信登录显示的方法wxLogin() {//console.log('************'+this.token)//把token值放到cookie里面cookie.set('token',this.token,{domain: 'localhost'})cookie.set('user-center','',{domain: 'localhost'})//console.log('====='+cookie.get('guli_token'))//调用接口,根据token值获取用户信息loginApi.getUserInfo().then(response => {//console.log('################'+response.data.data.userInfo)this.loginInfo = response.datacookie.set('user-center',this.loginInfo,{domain: 'localhost'})})},//创建方法,从cookie获取用户信息showInfo() {//从cookie获取用户信息var userStr = cookie.get('user-center')// 把字符串转换json对象(js对象)if(userStr) {this.loginInfo = JSON.parse(userStr)}},//退出logout() {//清空cookie值cookie.set('token','',{domain: 'localhost'})cookie.set('user-center','',{domain: 'localhost'})//回到首页面window.location.href = "/";}}
3.用户微服务
微信扫码登录

OAuth2

是针对特定问题的一种解决方案
主要解决两个问题
- 开放系统间的授权
- 分布式访问问题
优势

误解

令牌类型

OAuth2提出的背景
照片拥有者想要在云冲印服务上打印照片,云冲印服务需要访问云存储服务上的资源

资源拥有者:照片拥有者
客户应用:云冲印
受保护的资源:图片

方式一:用户名密码复制
适用于同一公司内部的多个系统,不适用于不受信的第三方应用

方式二:通用开发者key
适用于合作商或者授信不同业务的部门之间
方式三:办法令牌
接近OAuth2方式,需要考虑如何管理令牌、颁发令牌、吊销令牌,需要统一的协议,因此就有了OAuth2协议
wx

微信支付

weixin:pay:#关联的公众号appidappid: wx74862e0dfcf69954#商户号partner: 1558950191#商户keypartnerkey: T6m9iK73b0kn9g5v426MKfHQH7X8rKwb#回调地址notifyurl: http://guli.shop/api/order/weixinPay/weixinNotify
引入依赖
<dependency><groupId>com.github.wxpay</groupId><artifactId>wxpay-sdk</artifactId><version>0.0.3</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId></dependency>
工具类 HttpClient
package cn.cinz.utils;import org.apache.http.Consts;import org.apache.http.HttpEntity;import org.apache.http.NameValuePair;import org.apache.http.client.ClientProtocolException;import org.apache.http.client.entity.UrlEncodedFormEntity;import org.apache.http.client.methods.*;import org.apache.http.conn.ssl.SSLConnectionSocketFactory;import org.apache.http.conn.ssl.SSLContextBuilder;import org.apache.http.conn.ssl.TrustStrategy;import org.apache.http.entity.StringEntity;import org.apache.http.impl.client.CloseableHttpClient;import org.apache.http.impl.client.HttpClients;import org.apache.http.message.BasicNameValuePair;import org.apache.http.util.EntityUtils;import javax.net.ssl.SSLContext;import java.io.IOException;import java.security.cert.CertificateException;import java.security.cert.X509Certificate;import java.text.ParseException;import java.util.HashMap;import java.util.LinkedList;import java.util.List;import java.util.Map;/*** http请求客户端** @author qy**/public class HttpClient {private String url;private Map<String, String> param;private int statusCode;private String content;private String xmlParam;private boolean isHttps;public boolean isHttps() {return isHttps;}public void setHttps(boolean isHttps) {this.isHttps = isHttps;}public String getXmlParam() {return xmlParam;}public void setXmlParam(String xmlParam) {this.xmlParam = xmlParam;}public HttpClient(String url, Map<String, String> param) {this.url = url;this.param = param;}public HttpClient(String url) {this.url = url;}public void setParameter(Map<String, String> map) {param = map;}public void addParameter(String key, String value) {if (param == null)param = new HashMap<String, String>();param.put(key, value);}public void post() throws ClientProtocolException, IOException {HttpPost http = new HttpPost(url);setEntity(http);execute(http);}public void put() throws ClientProtocolException, IOException {HttpPut http = new HttpPut(url);setEntity(http);execute(http);}public void get() throws ClientProtocolException, IOException {if (param != null) {StringBuilder url = new StringBuilder(this.url);boolean isFirst = true;for (String key : param.keySet()) {if (isFirst)url.append("?");elseurl.append("&");url.append(key).append("=").append(param.get(key));}this.url = url.toString();}HttpGet http = new HttpGet(url);execute(http);}/*** set http post,put param*/private void setEntity(HttpEntityEnclosingRequestBase http) {if (param != null) {List<NameValuePair> nvps = new LinkedList<NameValuePair>();for (String key : param.keySet())nvps.add(new BasicNameValuePair(key, param.get(key))); // 参数http.setEntity(new UrlEncodedFormEntity(nvps, Consts.UTF_8)); // 设置参数}if (xmlParam != null) {http.setEntity(new StringEntity(xmlParam, Consts.UTF_8));}}private void execute(HttpUriRequest http) throws ClientProtocolException,IOException {CloseableHttpClient httpClient = null;try {if (isHttps) {SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {// 信任所有public boolean isTrusted(X509Certificate[] chain,String authType)throws CertificateException {return true;}}).build();SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext);httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).build();} else {httpClient = HttpClients.createDefault();}CloseableHttpResponse response = httpClient.execute(http);try {if (response != null) {if (response.getStatusLine() != null)statusCode = response.getStatusLine().getStatusCode();HttpEntity entity = response.getEntity();// 响应内容content = EntityUtils.toString(entity, Consts.UTF_8);}} finally {response.close();}} catch (Exception e) {e.printStackTrace();} finally {httpClient.close();}}public int getStatusCode() {return statusCode;}public String getContent() throws ParseException, IOException {return content;}}
创建微信支付订单,传入生成的订单号
//这是接口的实现类@Overridepublic Result getQRCode(String orderNo) {try {//1.根据订单号查询订单信息Order order = orderService.getOrderByOrderNo(orderNo);//2.使用map设置生成二维码需要参数Map<String,String> map = new HashMap<>();map.put("appid","wx74862e0dfcf69954");map.put("mch_id", "1558950191");map.put("nonce_str", WXPayUtil.generateNonceStr());//UIDmap.put("body", order.getCourseTitle());map.put("out_trade_no", orderNo); //订单号map.put("total_fee", order.getTotalFee().multiply(new BigDecimal("100")).longValue()+"");//订单价格map.put("spbill_create_ip", "127.0.0.1"); //本地调试map.put("notify_url", "http://guli.shop/api/order/weixiPay/weixinNotify\n");map.put("trade_type", "NATIVE");//3.发送httpclient请求,传递参数xml格式,微信支付提供的固定地址HttpClient httpClient = new HttpClient("https://api.mch.weixin.qq.com/pay/unifiedorder");//设置xml格式的参数httpClient.setXmlParam(WXPayUtil.generateSignedXml(map, "T6m9iK73b0kn9g5v426MKfHQH7X8rKwb"));//设置签名和商户keyhttpClient.setHttps(true);//执行请求httpClient.post();//4.得到发送请求返回结果//返回内容是xml格式String xml = httpClient.getContent();//把xml格式转换map集合Map<String,String> resultMap = WXPayUtil.xmlToMap(xml);//最终返回数据的封装Map<String,String> result = new HashMap<>();result.put("out_trade_no", orderNo);result.put("course_id", order.getCourseId());result.put("total_fee", String.valueOf(order.getTotalFee()));result.put("result_code", resultMap.get("result_code")); // 返回二维码操作状态码result.put("code_url", resultMap.get("code_url")); // 二维码地址return Result.SUCCESS().data(result);} catch (Exception e) {e.printStackTrace();throw new BaseException();}}
定时任务
1.启动类上添加注解@EnableScheduling //开启定时任务
@EnableScheduling //开启定时任务
2.创建定时任务类
在这个类里面,使用表达式设置什么时候去执行
cron表达式设置执行规则@Scheduled(cron = "0/5 * * * * ?")每隔五秒执行一次
import cn.cinz.statistics.service.StatisticsDailyService;import cn.cinz.utils.DateUtil;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.scheduling.annotation.Scheduled;import org.springframework.stereotype.Component;import java.util.Date;@Componentpublic class ScheduledTask {private final Logger logger = LoggerFactory.getLogger(ScheduledTask.class);@Autowiredprivate StatisticsDailyService statisticsDailyService;// 每隔五秒执行一次// @Scheduled(cron = "0/5 * * * * ?")// public void task1(){// logger.info("执行了定时任务");// }//每天凌晨0点,把前一天数据进行添加@Scheduled(cron = "0 0 0 1/1 * ?")public void task(){logger.info("每天零点执行任务统计任务");statisticsDailyService.getRegisterDayCount(DateUtil.getYestdayStr(new Date()));}}
3.在线生成cron表达式
4.分布式任务调度平台 xxl-job
Canal
1.应用场景
在前面的统计分析功能中,我们采取了服务调用获取统计数据,这样耦合度高,效率相对较低,目前我采取另一种实现方式,通过实时同步数据库表的方式实现,例如我们要统计每天注册与登录人数,我们只需把会员表同步到统计库中,实现本地统计就可以了,这样效率更高,耦合度更低,canal就是一个很好的数据库同步工具。canal是阿里巴巴旗下的一款开源项目,纯Java开发。基于数据库增量日志解析,提供增量数据订阅&消费,目前主要支持了MySQL。
2.Canal环境搭建
canal的原理是基于mysql binlog技术,所以这里一定需要开启mysql的binlog写入功能开启mysql服务:service mysql start(或者systemctl start mysqld.service )
spring security

jenkins
必备环境
java环境
#安装jdk8$ yum install -y java-1.8.0-openjdk.x86_64# 环境变量配置$ vim /etc/profile# 添加如下信息export JAVA_HOME=/usr/local/jdkexport JRE_HOME=$JAVA_HOME/jreexport CLASSPATH=./$CLASSPATH:$JAVA_HOME/lib:$JRE_HOME/libexport PATH=$PATH:$JAVA_HOME/bin:$JRE_HOME/bin#保存后,让文件立即生效$ source /etc/profile
安装maven
安装docker
更新并安装Docker-CE
$ yum makecache fast$ yum -y install docker-ce#开启docker服务$ service docker start#测试是否安装成功$ docker -v
服务器部署
1.安装nacos
官方文档:https://nacos.io/zh-cn/docs/quick-start.html
#下载nacos,我下载的是1.4.3的版本#服务器选择nacos-server-1.4.3.tar.gzhttps://github.com/alibaba/nacos/releases#放置服务器目录如:/usr/local下,解压后的目录结构为/usr/local/nacos$ tar -zxvf nacos-server-1.4.3.tar.gz
a.修改nacos/conf目录下的application.properties
$ vim nacos/conf/application.properties

b.编辑 vim startup.sh脚本,修改

保存退出。
c.首先创建一个数据库nacos,然后运行数据库脚本,在源码包有,在 nacos/conf/nacos-mysql.sql,把这个导入到数据中
d.启动nacos
- 注:Nacos的运行需要以至少2C4g60g*3的机器配置下运行。
运行文件在nacos/bin下
#Linux/Unix/Mac#启动命令(standalone代表着单机模式运行,非集群模式):$ sh startup.sh -m standalone#如果您使用的是ubuntu系统,或者运行脚本报错提示[[符号找不到,可尝试如下运行:$ bash startup.sh -m standalone$Windows启动命令(standalone代表着单机模式运行,非集群模式):$ startup.cmd -m standalone
e.关闭nacos
运行文件在nacos/bin下
#Linux/Unix/Mac$sh shutdown.sh#Windows$shutdown.cmd#或者双击shutdown.cmd运行文件。
f.将nacos config配置中心的文件导入

2.安装jdk
修改环境变量 vim /etc/profile
添加如下信息
export JAVA_HOME=jdk安装位置export PATH=$JAVA_HOME/bin:$PATHexport CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
然后刷新下文件 source /etc/profile
查看是否配置成功,使用 java -version 和 echo $JAVA_HOME 分别查看
3.打包本地jar,放置服务器运行
运行脚本可参考
$ nohup java -jar -Xms50m -Xmx100m -XX:PermSize=64M -XX:MaxPermSize=128M /root/app/halo.jar > nohup 2>&1 &
解读:后台不间断运行java程序 并将日志文件输出到 nohub 中
列出所有包含 .jar java 的进程
$ ps -ef | grep java | grep .jar
强制关闭掉包含 .jar java 的进程
$ ps -ef | grep java | grep .jar | xargs kill -9
4.安装redis
因为项目中有使用redis,这里需要安装
5.安装mysql
mysql安装选择8.*版本
6.安装nginx
配置nginx.conf文件
#*.*.*.* 为服务器ip地址location ~ /admin/acl{#最终转发到服务器的地址proxy_pass http://*.*.*.*:8011;}location ~ /api/{#最终转发到服务器的地址proxy_pass http://*.*.*.*:8011;}
