认证服务------功能实现逻辑

news2025/1/4 17:28:16

认证服务分为两种,一种是社交登录,一种是单点登录,都交给认证中心(OAuth2.0)来处理,用一个专门的服务来作这个认证中心,所有的登录注册都是由这个认证中心来处理的,由于注册要发短信,短信又是调用了阿里云的接口, 这个发验证码短信的组件放到了第三方服务中,所以又需要调用第三方服务来发短信,最终的登录注册实现是写在用户服务里的,所以还需要远程调用用户服务

单点登录注册

一、用户注册功能(登录和注册都是以表单提交发的请求)

1、实现发送手机验证码功能(手机验证码是存到Redis缓存中)

(1)前端实现发送短信后倒计时效果和发送请求/sms/sendCode给后端处理

		<div class="register-box">
			<label class="other_label">验 证 码:
				<input name="code" maxlength="20" type="text"  class="caa">
			</label>
			<a id="sendCode" class=""> 发送验证码 </a>
		</div>
	/**
	 * 发送验证码请求给后端
	 */
	$(function () {
    	$("#sendCode").click(function () {
			//2、倒计时
			//只有class里有disabled就表示还在倒计时中,什么都不干(用于防止在倒计时期间重复发送验证码)
			if($(this).hasClass("disabled")) {
				//正在倒计时中
			} else {
				//1、给指定手机号发送验证码
				$.get("/sms/sendCode?phone=" + $("#phoneNum").val(),function (data) {
					if(data.code != 0) {
						alert(data.msg);
					}
				});
				timeoutChangeStyle();
			}
		});
    });

	/**
	 * 实现验证码倒计时效果
	 * @type {number}
	 */
    var num = 60; 	//倒计时60s
    function timeoutChangeStyle() {
    	//点击发送验证码后就设置其class为disabled(用于防止在倒计时期间重复发送验证码)
    	$("#sendCode").attr("class","disabled");
    	//倒计时结束
    	if(num == 0) {
			$("#sendCode").text("发送验证码");
			num = 60;
			//清空前面加的class
			$("#sendCode").attr("class","");
		} else {
			var str = num + "s 后再次发送";
			$("#sendCode").text(str);
			//1s后再调用自身方法来实现倒计时
			setTimeout("timeoutChangeStyle()",1000);
		}
		num --;
	}

(2)在认证服务的LoginController来处理请求/sms/sendCode(发短信验证码)

分流程:(其实就是把短信验证码以下面的格式存到缓存再真正发送短信验证码

Redis缓存中验证码的格式sms:code:123456789->123456_1646981054661 ,123456789是手机号,123456表示验证码,1646981054661表示存入缓存的时间

1>判断Redis缓存里有没有这个手机号对应的验证码信息

2>如果Redis缓存中有这个验证码信息那就要判断时间合法性(其实就是验证码有没有过期)

3>创建验证码并且拼装成存入Redis缓存中的value值格式

4>调用第三方服务来真正发送验证码

/**
* 短信验证码
* @param phone
* @return
*/
@ResponseBody
@GetMapping(value = "/sms/sendCode")
public R sendCode(@RequestParam("phone") String phone) {
    
    /**
    *  接口防刷
    */
    
    //把验证码从缓存中提取出来
    //(缓存中验证码的格式sms:code:123456789->123456_1646981054661 ,123456789是手机号,123456表示验证码,1646981054661表示存入缓存的时间)
    String redisCode = stringRedisTemplate.opsForValue().get(AuthServerConstant.SMS_CODE_CACHE_PREFIX + phone);
    if (!StringUtils.isEmpty(redisCode)) {
        //把存入缓存的验证码的值给提取出来(格式是123456_1646981054661 ,123456表示验证码,1646981054661表示存入缓存的时间)
        long currentTime = Long.parseLong(redisCode.split("_")[1]);
        //活动存入redis的时间,用当前时间减去存入redis的时间,判断用户手机号是否在60s内发送验证码
        if (System.currentTimeMillis() - currentTime < 60000) {
            //60s内不能再发
            // BizCodeEnum.SMS_CODE_EXCEPTION=10002     BizCodeEnum.SMS_CODE_EXCEPTION = 10002
            return R.error(BizCodeEnum.SMS_CODE_EXCEPTION.getCode(),BizCodeEnum.SMS_CODE_EXCEPTION.getMessage());
        }
    }
    
    /**
    * 2、创建验证码存入redis.存key-phone,value-code
    */
    int code = (int) ((Math.random() * 9 + 1) * 100000);
    String codeNum = String.valueOf(code);
    //存入缓存的验证码格式是123456_1646981054661  加系统时间是为了防止多次刷新验证码
    String redisStorage = codeNum + "_" + System.currentTimeMillis();
    
    //存入redis,防止同一个手机号在60秒内再次发送验证码(存入缓存的格式sms:code:123456789->123456 ,其中123456789是手机号,123456是验证码)
    stringRedisTemplate.opsForValue().set(AuthServerConstant.SMS_CODE_CACHE_PREFIX+phone,
                                          redisStorage,10, TimeUnit.MINUTES);//AuthServerConstant.SMS_CODE_CACHE_PREFIX = sms:code:
    
    //        String codeNum = UUID.randomUUID().toString().substring(0,6);
    thirdPartFeignService.sendCode(phone, codeNum);
    
    return R.ok();
    }

注意:这里的短信验证码不是第三方服务发送的,具体短信验证码的内容是上面的sendCode方法随机生成的,这个验证码甚至可以自定义为666666,所以这个验证码的内容在上面sendCode方法就已经存到Redis缓存中,方便后面进行验证码校验

(3)远程调用第三方服务来真正发送验证码

package com.saodai.saodaimall.auth.feign;


import com.saodai.common.utils.R;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

/**
 * 远程调用第三方服务来发送验证码
 **/

@FeignClient("saodaimall-third-party")
public interface ThirdPartFeignService {

    @GetMapping(value = "/sms/sendCode")
    R sendCode(@RequestParam("phone") String phone, @RequestParam("code") String code);

}
package com.saodai.saodaimall.thirdparty.controller;


import com.saodai.common.utils.R;
import com.saodai.saodaimall.thirdparty.component.SmsComponent;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

/**
 * 短信验证码的控制器
 **/

@RestController
@RequestMapping(value = "/sms")
public class SmsSendController {

    @Resource
    private SmsComponent smsComponent;

    /**
     * 提供给别的服务进行调用
     * @param phone
     * @param code
     * @return
     */
    @GetMapping(value = "/sendCode")
    public R sendCode(@RequestParam("phone") String phone, @RequestParam("code") String code) {

        //发送验证码
        smsComponent.sendCode(phone,code);

        return R.ok();
    }

}
package com.saodai.saodaimall.thirdparty.component;


import com.saodai.saodaimall.thirdparty.util.HttpUtils;
import lombok.Data;
import org.apache.http.HttpResponse;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

/**
 *  短信验证码发送的组件类(提供发送短信验证码的接口)
 **/

@ConfigurationProperties(prefix = "spring.cloud.alicloud.sms")
@Data
@Component
public class SmsComponent {

    private String host;
    private String path;
    private String appcode;

    public void sendCode(String phone,String code) {
        String method = "POST";
        Map<String, String> headers = new HashMap<String, String>();
        //最后在header中的格式(中间是英文空格)为Authorization:APPCODE 83359fd73fe94948385f570e3c139105
        headers.put("Authorization", "APPCODE " + appcode);
        //根据API的要求,定义相对应的Content-Type
        headers.put("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
        Map<String, String> querys = new HashMap<String, String>();
        Map<String, String> bodys = new HashMap<String, String>();
        //注意这里的"code:"是必须要有的,正确的格式是"code:6666",没加code:就会报400错误,expire_at:6表示验证码有效时间为6分钟
        bodys.put("content", "code:"+code+",expire_at:6");
        bodys.put("phone_number", phone);
        //使用的是自定义的模板(content需要两个参数)
        bodys.put("template_id", "TPL_09229");



        try {
            HttpResponse response = HttpUtils.doPost(host, path, method, headers, querys, bodys);
            System.out.println(response.toString());
            //获取response的body
            //System.out.println(EntityUtils.toString(response.getEntity()));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}
package com.saodai.saodaimall.thirdparty.util;

import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
* 阿里云短信验证码接口要用的工具类
*/
public class HttpUtils {
    
    /**
    * get
    * 
    * @param host
    * @param path
    * @param method
    * @param headers
    * @param querys
    * @return
    * @throws Exception
    */
    public static HttpResponse doGet(String host, String path, String method, 
                                     Map<String, String> headers, 
                                     Map<String, String> querys)
        throws Exception {    	
        HttpClient httpClient = wrapClient(host);
        
        HttpGet request = new HttpGet(buildUrl(host, path, querys));
        for (Map.Entry<String, String> e : headers.entrySet()) {
            request.addHeader(e.getKey(), e.getValue());
        }
        
        return httpClient.execute(request);
    }
    
    /**
    * post form
    * 
    * @param host
    * @param path
    * @param method
    * @param headers
    * @param querys
    * @param bodys
    * @return
    * @throws Exception
    */
    public static HttpResponse doPost(String host, String path, String method, 
                                      Map<String, String> headers, 
                                      Map<String, String> querys, 
                                      Map<String, String> bodys)
        throws Exception {    	
        HttpClient httpClient = wrapClient(host);
        
        HttpPost request = new HttpPost(buildUrl(host, path, querys));
        for (Map.Entry<String, String> e : headers.entrySet()) {
            request.addHeader(e.getKey(), e.getValue());
        }
        
        if (bodys != null) {
            List<NameValuePair> nameValuePairList = new ArrayList<NameValuePair>();
            
            for (String key : bodys.keySet()) {
                nameValuePairList.add(new BasicNameValuePair(key, bodys.get(key)));
            }
            UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(nameValuePairList, "utf-8");
            formEntity.setContentType("application/x-www-form-urlencoded; charset=UTF-8");
            request.setEntity(formEntity);
        }
        
        return httpClient.execute(request);
    }	
    
    /**
    * Post String
    * 
    * @param host
    * @param path
    * @param method
    * @param headers
    * @param querys
    * @param body
    * @return
    * @throws Exception
    */
    public static HttpResponse doPost(String host, String path, String method, 
                                      Map<String, String> headers, 
                                      Map<String, String> querys, 
                                      String body)
        throws Exception {    	
        HttpClient httpClient = wrapClient(host);
        
        HttpPost request = new HttpPost(buildUrl(host, path, querys));
        for (Map.Entry<String, String> e : headers.entrySet()) {
            request.addHeader(e.getKey(), e.getValue());
        }
        
        if (StringUtils.isNotBlank(body)) {
            request.setEntity(new StringEntity(body, "utf-8"));
        }
        
        return httpClient.execute(request);
    }
    
    /**
    * Post stream
    * 
    * @param host
    * @param path
    * @param method
    * @param headers
    * @param querys
    * @param body
    * @return
    * @throws Exception
    */
    public static HttpResponse doPost(String host, String path, String method, 
                                      Map<String, String> headers, 
                                      Map<String, String> querys, 
                                      byte[] body)
        throws Exception {    	
        HttpClient httpClient = wrapClient(host);
        
        HttpPost request = new HttpPost(buildUrl(host, path, querys));
        for (Map.Entry<String, String> e : headers.entrySet()) {
            request.addHeader(e.getKey(), e.getValue());
        }
        
        if (body != null) {
            request.setEntity(new ByteArrayEntity(body));
        }
        
        return httpClient.execute(request);
    }
    
    /**
    * Put String
    * @param host
    * @param path
    * @param method
    * @param headers
    * @param querys
    * @param body
    * @return
    * @throws Exception
    */
    public static HttpResponse doPut(String host, String path, String method, 
                                     Map<String, String> headers, 
                                     Map<String, String> querys, 
                                     String body)
        throws Exception {    	
        HttpClient httpClient = wrapClient(host);
        
        HttpPut request = new HttpPut(buildUrl(host, path, querys));
        for (Map.Entry<String, String> e : headers.entrySet()) {
            request.addHeader(e.getKey(), e.getValue());
        }
        
        if (StringUtils.isNotBlank(body)) {
            request.setEntity(new StringEntity(body, "utf-8"));
        }
        
        return httpClient.execute(request);
    }
    
    /**
    * Put stream
    * @param host
    * @param path
    * @param method
    * @param headers
    * @param querys
    * @param body
    * @return
    * @throws Exception
    */
    public static HttpResponse doPut(String host, String path, String method, 
                                     Map<String, String> headers, 
                                     Map<String, String> querys, 
                                     byte[] body)
        throws Exception {    	
        HttpClient httpClient = wrapClient(host);
        
        HttpPut request = new HttpPut(buildUrl(host, path, querys));
        for (Map.Entry<String, String> e : headers.entrySet()) {
            request.addHeader(e.getKey(), e.getValue());
        }
        
        if (body != null) {
            request.setEntity(new ByteArrayEntity(body));
        }
        
        return httpClient.execute(request);
    }
    
    /**
    * Delete
    *  
    * @param host
    * @param path
    * @param method
    * @param headers
    * @param querys
    * @return
    * @throws Exception
    */
    public static HttpResponse doDelete(String host, String path, String method, 
                                        Map<String, String> headers, 
                                        Map<String, String> querys)
        throws Exception {    	
        HttpClient httpClient = wrapClient(host);
        
        HttpDelete request = new HttpDelete(buildUrl(host, path, querys));
        for (Map.Entry<String, String> e : headers.entrySet()) {
            request.addHeader(e.getKey(), e.getValue());
        }
        
        return httpClient.execute(request);
    }
    
    private static String buildUrl(String host, String path, Map<String, String> querys) throws UnsupportedEncodingException {
        StringBuilder sbUrl = new StringBuilder();
        sbUrl.append(host);
        if (!StringUtils.isBlank(path)) {
            sbUrl.append(path);
        }
        if (null != querys) {
            StringBuilder sbQuery = new StringBuilder();
            for (Map.Entry<String, String> query : querys.entrySet()) {
                if (0 < sbQuery.length()) {
                    sbQuery.append("&");
                }
                if (StringUtils.isBlank(query.getKey()) && !StringUtils.isBlank(query.getValue())) {
                    sbQuery.append(query.getValue());
                }
                if (!StringUtils.isBlank(query.getKey())) {
                    sbQuery.append(query.getKey());
                    if (!StringUtils.isBlank(query.getValue())) {
                        sbQuery.append("=");
                        sbQuery.append(URLEncoder.encode(query.getValue(), "utf-8"));
                    }        			
                }
            }
            if (0 < sbQuery.length()) {
                sbUrl.append("?").append(sbQuery);
            }
        }
        
        return sbUrl.toString();
    }
    
    private static HttpClient wrapClient(String host) {
        HttpClient httpClient = new DefaultHttpClient();
        if (host.startsWith("https://")) {
            sslClient(httpClient);
        }
        
        return httpClient;
    }
    
    private static void sslClient(HttpClient httpClient) {
        try {
            SSLContext ctx = SSLContext.getInstance("TLS");
            X509TrustManager tm = new X509TrustManager() {
                public X509Certificate[] getAcceptedIssuers() {
                    return null;
                }
                public void checkClientTrusted(X509Certificate[] xcs, String str) {
                    
                }
                public void checkServerTrusted(X509Certificate[] xcs, String str) {
                    
                }
            };
            ctx.init(null, new TrustManager[] { tm }, null);
            SSLSocketFactory ssf = new SSLSocketFactory(ctx);
            ssf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
            ClientConnectionManager ccm = httpClient.getConnectionManager();
            SchemeRegistry registry = ccm.getSchemeRegistry();
            registry.register(new Scheme("https", 443, ssf));
        } catch (KeyManagementException ex) {
            throw new RuntimeException(ex);
        } catch (NoSuchAlgorithmException ex) {
            throw new RuntimeException(ex);
        }
    }
}
<!--        阿里云对象存储,用于上传文件-->
<dependency>
  <groupId>com.alibaba.cloud</groupId>
  <artifactId>spring-cloud-starter-alicloud-oss</artifactId>
  <version>2.2.0.RELEASE</version>
</dependency>
spring:
  cloud:
    alicloud:
      #短信验证码的配置
      sms:
        host: https://dfsns.market.alicloudapi.com
        path: /data/send_sms
        appcode: dbc663defb63426caefc7be0a8fe8cdf

整个配置文件里就只有appcode是自己购买短信服务后唯一的码,其他的都是固定配置

AppCode获取地址:阿里云登录 - 欢迎登录阿里云,安全稳定的云计算服务平台

2、注册用户

(1)前端以表单的形式发送请求/register给后端

<form action="/register" method="post" class="one">
<!--		<div style="color: red" th:text="${errors != null ? (#maps.containsKey(errors, 'msg') ? errors.msg : '') : ''}">-->
		<div class="register-box">
			<label class="username_label">用 户 名:
				<input name="userName" maxlength="20" type="text" >
			</label>
<!--			#maps.containsKey(errors, 'userName')表示errors这个map里包含userName的话就取出报错信息,没有的话就输出空字符串-->
			<div class="tips" style="color: red" th:text="${errors != null ? (#maps.containsKey(errors, 'userName') ? errors.userName : '') : ''}">
				错误提示区域,如果出错了就会从后端获取报错信息
			</div>
		</div>
		<div class="register-box">
			<label class="other_label">设 置 密 码:
				<input name="password" maxlength="20" type="password" >
			</label>
			<div class="tips" style="color: red" th:text="${errors != null ? (#maps.containsKey(errors, 'password') ? errors.password : '') : ''}">

			</div>
		</div>
		<div class="register-box">
			<label class="other_label">确 认 密 码:
				<input maxlength="20" type="password" >
			</label>
			<div class="tips">

			</div>
		</div>
		<div class="register-box">
			<label class="other_label">
				<span>中国 0086∨</span>
				<input name="phone" class="phone" id="phoneNum" maxlength="20" type="text" >
			</label>
			<div class="tips" style="color: red" th:text="${errors != null ? (#maps.containsKey(errors, 'phone') ? errors.phone : '') : ''}">

			</div>
		</div>
		<div class="register-box">
			<label class="other_label">验 证 码:
				<input name="code" maxlength="20" type="text"  class="caa">
			</label>
			<a id="sendCode" class=""> 发送验证码 </a>
		</div>
		<div class="arguement">
			<input type="checkbox" id="xieyi"> 阅读并同意
			<a href="/static/reg/#">《谷粒商城用户注册协议》</a>
			<a href="/static/reg/#">《隐私政策》</a>
			<div class="tips" style="color: red" th:text="${errors != null ? (#maps.containsKey(errors, 'code') ? errors.code : '') : ''}">

			</div>
			<br/>
			<div class="submit_btn">
				<button type="submit" id="submit_btn">立 即 注 册</button>
			</div>
		</div>
	</form>

(2)在认证服务的LoginController来处理请求/register

分流程:

1>获取用户输入的验证码和缓存里的验证码(这里要判断缓存中验证码为不为空)进行比较,看是否相同

2>验证码相同就立马删除验证码(令牌机制),验证码不同报错同时重定向到注册界面

3>远程调用会员服务进行真正的用户注册

4>远程调用成功就重定向到首页,失败就把错误消息封装成map同时重定向到注册界面

/**
*   用户注册
* TODO: 重定向携带数据:利用session原理,将数据放在session中。
* TODO:只要跳转到下一个页面取出这个数据以后,session里面的数据就会删掉
* TODO:分布下session问题
* RedirectAttributes:重定向也可以保留数据,不会丢失
*
* @return
*/
@PostMapping(value = "/register")
public String register(@Valid UserRegisterVo vos, BindingResult result,
                       RedirectAttributes attributes) {
    
    //1、效验验证码
    String code = vos.getCode();
    
    //获取存入Redis里的验证码(redisCode的格式是123456_1646981054661 ,123456表示验证码,1646981054661表示存入缓存的时间)
    String redisCode = stringRedisTemplate.opsForValue().get(AuthServerConstant.SMS_CODE_CACHE_PREFIX + vos.getPhone());
    //判断redis的对应的验证码是否为空
    if (!StringUtils.isEmpty(redisCode)) {
        //判断验证码是否相同
        if (code.equals(redisCode.split("_")[0])) {
            //删除验证码;令牌机制
            stringRedisTemplate.delete(AuthServerConstant.SMS_CODE_CACHE_PREFIX+vos.getPhone());
            //验证码通过,真正注册,调用远程服务进行注册
            R register = memberFeignService.register(vos);
            //register.getCode() == 0表示远程调用成功
            if (register.getCode() == 0) {
                //成功
                return "redirect:http://saodaimall.com";
            } else {
                //失败
                Map<String, String> errors = new HashMap<>();
                errors.put("msg", register.getData("msg",new TypeReference<String>(){}));
                attributes.addFlashAttribute("errors",errors);
                return "redirect:http://auth.saodaimall.com/reg.html";
            }
            
            
        } else {
            //效验出错回到注册页面
            Map<String, String> errors = new HashMap<>();
            errors.put("code","验证码错误");
            attributes.addFlashAttribute("errors",errors);
            return "redirect:http://auth.saodaimall.com/reg.html";
        }
    } else {
        //redis的对应的验证码为空表示效验出错回到注册页面
        Map<String, String> errors = new HashMap<>();
        errors.put("code","验证码错误");
        attributes.addFlashAttribute("errors",errors);
        return "redirect:http://auth.saodaimall.com/reg.html";
    }
}
package com.saodai.saodaimall.auth.vo;

import lombok.Data;
import org.hibernate.validator.constraints.Length;

import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Pattern;

/**
 * 用户注册信息封装类
 **/

@Data
public class UserRegisterVo {

    @NotEmpty(message = "用户名不能为空")
    @Length(min = 6, max = 19, message="用户名长度在6-18字符")
    private String userName;

    @NotEmpty(message = "密码必须填写")
    @Length(min = 6,max = 18,message = "密码必须是6—18位字符")
    private String password;

    @NotEmpty(message = "手机号不能为空")
    @Pattern(regexp = "^[1]([3-9])[0-9]{9}$", message = "手机号格式不正确")
    private String phone;

    @NotEmpty(message = "验证码不能为空")
    private String code;

}

(3)远程调用会员服务来实现真正的注册

/**
     * 会员注册功能
     * @param vo
     * @return
     */
    @PostMapping(value = "/register")
    public R register(@RequestBody MemberUserRegisterVo vo) {

        try {
            memberService.register(vo);
        } catch (PhoneException e) {
            //BizCodeEnum.PHONE_EXIST_EXCEPTION=存在相同的手机号  15002
            return R.error(BizCodeEnum.PHONE_EXIST_EXCEPTION.getCode(),BizCodeEnum.PHONE_EXIST_EXCEPTION.getMessage());
        } catch (UsernameException e) {
            //BizCodeEnum.USER_EXIST_EXCEPTION=商品库存不足  21000
            return R.error(BizCodeEnum.USER_EXIST_EXCEPTION.getCode(),BizCodeEnum.USER_EXIST_EXCEPTION.getMessage());
        }

        return R.ok();
    }
    /**
     *    会员注册
     */

    @Override
    public void register(MemberUserRegisterVo vo) {

        MemberEntity memberEntity = new MemberEntity();

        //设置默认等级
        MemberLevelEntity levelEntity = memberLevelDao.getDefaultLevel();
        memberEntity.setLevelId(levelEntity.getId());

        //设置其它的默认信息
        //检查用户名和手机号是否唯一。感知异常,异常机制(异常机制就是问题就抛出具体异常,没问题就继续执行下面的语句)
        checkPhoneUnique(vo.getPhone());
        checkUserNameUnique(vo.getUserName());

        memberEntity.setNickname(vo.getUserName());
        memberEntity.setUsername(vo.getUserName());
        //密码进行MD5盐值加密(盐值加密同一个数据的每次加密结果是不一样的,通过match方法来密码校验)
        // (注意这里不能用md5直接加密放数据库,因为彩虹表可以破解md5,所谓彩虹表就是通过大量的md5数据反向退出md5
        // 注意MD5是不可逆,但是可暴力通过彩虹表破解)
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        String encode = bCryptPasswordEncoder.encode(vo.getPassword());
        memberEntity.setPassword(encode);
        memberEntity.setMobile(vo.getPhone());
        memberEntity.setGender(0);
        memberEntity.setCreateTime(new Date());

        //保存数据
        this.baseMapper.insert(memberEntity);
    }

    /**
     * 检查手机号是否重复的异常机制方法
     * @param phone
     * @throws PhoneException
     */
    @Override
    public void checkPhoneUnique(String phone) throws PhoneException {

        Long phoneCount = this.baseMapper.selectCount(new QueryWrapper<MemberEntity>().eq("mobile", phone));
        //usernameCount > 0表示手机号已经存在
        if (phoneCount > 0) {
            throw new PhoneException();
        }

    }

    /**
     * 检查用户名是否重复的异常机制方法
     * @param userName
     * @throws UsernameException
     */
    @Override
    public void checkUserNameUnique(String userName) throws UsernameException {

        Long usernameCount = this.baseMapper.selectCount(new QueryWrapper<MemberEntity>().eq("username", userName));
        //usernameCount > 0表示用户名已经存在
        if (usernameCount > 0) {
            throw new UsernameException();
        }
    }

分流程:(封装MemberEntity对象)

1>设置默认等级

package com.saodai.saodaimall.member.entity;

import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.io.Serializable;
import java.math.BigDecimal;

/**
 * 会员等级
 */
@Data
@TableName("ums_member_level")
public class MemberLevelEntity implements Serializable {
	private static final long serialVersionUID = 1L;

	/**
	 * id
	 */
	@TableId
	private Long id;
	/**
	 * 等级名称
	 */
	private String name;
	/**
	 * 等级需要的成长值
	 */
	private Integer growthPoint;
	/**
	 * 是否为默认等级[0->不是;1->是]
	 */
	private Integer defaultStatus;
	/**
	 * 免运费标准
	 */
	private BigDecimal freeFreightPoint;
	/**
	 * 每次评价获取的成长值
	 */
	private Integer commentGrowthPoint;
	/**
	 * 是否有免邮特权
	 */
	private Integer priviledgeFreeFreight;
	/**
	 * 是否有会员价格特权
	 */
	private Integer priviledgeMemberPrice;
	/**
	 * 是否有生日特权
	 */
	private Integer priviledgeBirthday;
	/**
	 * 备注
	 */
	private String note;

}
<!--    查询出默认等级-->
<select id="getDefaultLevel" resultType="com.saodai.saodaimall.member.entity.MemberLevelEntity">
  SELECT * FROM ums_member_level WHERE default_status = 1
</select>
2>检查用户名和手机号是否唯一,通过异常机制来感知异常(异常机制就是问题就抛出具体异常,没问题就继续执行下面的语句)

package com.saodai.saodaimall.member.exception;


public class UsernameException extends RuntimeException {
    
    
    public UsernameException() {
        super("存在相同的用户名");
    }
}
package com.saodai.saodaimall.member.exception;


public class PhoneException extends RuntimeException {

    public PhoneException() {
        super("存在相同的手机号");
    }
}

3>密码进行MD5盐值加密

4>保存用户信息到数据库中

package com.saodai.saodaimall.member.vo;

import lombok.Data;

/**
 * 会员注册类
 **/

@Data
public class MemberUserRegisterVo {

    private String userName;

    private String password;

    private String phone;

}
package com.saodai.saodaimall.member.entity;

import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.io.Serializable;
import java.util.Date;

/**
 * 会员
 */
@Data
@TableName("ums_member")
public class MemberEntity implements Serializable {
	private static final long serialVersionUID = 1L;

	/**
	 * id
	 */
	@TableId
	private Long id;
	/**
	 * 会员等级id
	 */
	private Long levelId;
	/**
	 * 用户名
	 */
	private String username;
	/**
	 * 密码
	 */
	private String password;
	/**
	 * 昵称
	 */
	private String nickname;
	/**
	 * 手机号码
	 */
	private String mobile;
	/**
	 * 邮箱
	 */
	private String email;
	/**
	 * 头像
	 */
	private String header;
	/**
	 * 性别
	 */
	private Integer gender;
	/**
	 * 生日
	 */
	private Date birth;
	/**
	 * 所在城市
	 */
	private String city;
	/**
	 * 职业
	 */
	private String job;
	/**
	 * 个性签名
	 */
	private String sign;
	/**
	 * 用户来源
	 */
	private Integer sourceType;
	/**
	 * 积分
	 */
	private Integer integration;
	/**
	 * 成长值
	 */
	private Integer growth;
	/**
	 * 启用状态
	 */
	private Integer status;
	/**
	 * 注册时间
	 */
	private Date createTime;

	/**
	 * 社交登录用户的ID
	 */
	private String socialId;

	/**
	 * 社交登录用户的名称
	 */
	private String socialName;

	/**
	 * 社交登录用户的自我介绍
	 */
	private String socialBio;

}

二、用户登录功能

(1)前端以表单的形式发送请求/login给后端

<form action="/login" method="post">
  <div style="color: red;height: 10px;text-align: center;" th:text="${errors != null ? (#maps.containsKey(errors, 'msg') ? errors.msg : '') : ''}"></div>
  <ul>
    <li class="top_1">
      <img src="/static/login/JD_img/user_03.png" class="err_img1"/>
      <input type="text" name="loginacct" placeholder=" 邮箱/用户名/已验证手机" class="user"/>
    </li>
    <li>
      <img src="/static/login/JD_img/user_06.png" class="err_img2"/>
      <input type="password" name="password" placeholder=" 密码" class="password"/>
    </li>
    <li class="bri">
      <a href="/static/login/" >忘记密码</a>
    </li>
    <li class="ent">
      <button class="btn2" type="submit">登 &nbsp; &nbsp;录</a></button>
  </li>
 </ul>
</form>

(2)在认证服务的LoginController来处理请求/login

/**
* 普通手机账号登录
* @param vo
* @param attributes
* @param session
* @return
*/
@PostMapping(value = "/login")
public String login(UserLoginVo vo, RedirectAttributes attributes, HttpSession session) {
    
    //远程登录
    R login = memberFeignService.login(vo);
    
    //login.getCode() == 0表示远程调用成功
    if (login.getCode() == 0) {
        MemberResponseVo data = login.getData("data", new TypeReference<MemberResponseVo>() {});
        session.setAttribute(LOGIN_USER,data);
        return "redirect:http://saodaimall.com";
    } else {
        Map<String,String> errors = new HashMap<>();
        errors.put("msg",login.getData("msg",new TypeReference<String>(){}));
        attributes.addFlashAttribute("errors",errors);
        return "redirect:http://auth.saodaimall.com/login.html";
    }
    }
package com.saodai.saodaimall.auth.vo;

import lombok.Data;

/**
 * 用户登录信息封装类
 **/

@Data
public class UserLoginVo {
    //账户
    private String loginacct;
    //密码
    private String password;
}
package com.saodai.common.vo;

import lombok.Data;
import lombok.ToString;

import java.io.Serializable;
import java.util.Date;

/**
 *会员信息
 **/

@ToString
@Data
public class MemberResponseVo implements Serializable {

    private static final long serialVersionUID = 5573669251256409786L;

    private Long id;
    /**
     * 会员等级id
     */
    private Long levelId;
    /**
     * 用户名
     */
    private String username;
    /**
     * 密码
     */
    private String password;
    /**
     * 昵称
     */
    private String nickname;
    /**
     * 手机号码
     */
    private String mobile;
    /**
     * 邮箱
     */
    private String email;
    /**
     * 头像
     */
    private String header;
    /**
     * 性别
     */
    private Integer gender;
    /**
     * 生日
     */
    private Date birth;
    /**
     * 所在城市
     */
    private String city;
    /**
     * 职业
     */
    private String job;
    /**
     * 个性签名
     */
    private String sign;
    /**
     * 用户来源
     */
    private Integer sourceType;
    /**
     * 积分
     */
    private Integer integration;
    /**
     * 成长值
     */
    private Integer growth;
    /**
     * 启用状态
     */
    private Integer status;
    /**
     * 注册时间
     */
    private Date createTime;
    /**
     * 社交登录用户的ID
     */
    private String socialId;
    /**
     * 社交登录用户的名称
     */
    private String socialName;

    /**
     * 社交登录用户的自我介绍
     */
    private String socialBio;


}

(3)远程调用会员服务来实现真正的登录

 /**
     * 会员登录功能
     * @param vo
     * @return
     */
   @PostMapping(value = "/login")
    public R login(@RequestBody MemberUserLoginVo vo) {
       //MemberUserLoginVo就是上面的UserLoginVo只是类名不一样
        MemberEntity memberEntity = memberService.login(vo);

        if (memberEntity != null) {
            return R.ok().setData(memberEntity);
        } else {
            return R.error(BizCodeEnum.LOGINACCT_PASSWORD_EXCEPTION.getCode(),BizCodeEnum.LOGINACCT_PASSWORD_EXCEPTION.getMessage());
        }
    }
    /**
     * 会员登录功能
     * @param vo
     * @return
     */
    @Override
    public MemberEntity login(MemberUserLoginVo vo) {
         //获取用户登录账号
        String loginacct = vo.getLoginacct();
        String password = vo.getPassword();

        //1、去数据库查询 SELECT * FROM ums_member WHERE username = ? OR mobile = ?
        //通过用户名或手机号登录都是可以的
        MemberEntity memberEntity = this.baseMapper.selectOne(new QueryWrapper<MemberEntity>()
                .eq("username", loginacct).or().eq("mobile", loginacct));

        if (memberEntity == null) {
            //登录失败
            return null;
        } else {
            //获取到数据库里的password
            String password1 = memberEntity.getPassword();
            BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
            //进行密码匹配
            boolean matches = passwordEncoder.matches(password, password1);
            if (matches) {
                //登录成功
                return memberEntity;
            }
        }

        return null;
    }
package com.saodai.saodaimall.member.vo;

import lombok.Data;

/**
 * 会员登录
 **/

@Data
public class MemberUserLoginVo {

    private String loginacct;

    private String password;

}

社交登录(OAuth2.0)

这里由于微博开发者权限申请太慢了就使用gitee来实现社交登录

(1)前端调用第三方应用作为社交登录

这个跟以往的模式不一样,以往是前段直接给后端发送请求,然后后端处理请求,这个是先调用第三方应用作为社交登录(也就是先跳转到gitee的登录授权页面),然后用户登录自己的gitee账号密码进行授权,授权成功后会跳转到指定的应用回调地址,然后在后端来处理这个应用回调地址的请求

gitee开发者后台管理链接:https://gitee.com/oauth/applications/16285

调用gitee第三方登录的url地址:<a href="https://gitee.com/oauth/authorize?client_id=自己应用的Client ID&redirect_uri=自己应用的成功回调地址&response_type=code&state=1">

<li>
  <a href="https://gitee.com/oauth/authorize?client_id=32459f971ce6d89cfb9f70899525455d0653cb804f16b38a304e3447dc97d673&redirect_uri=http://auth.saodaimall.com/callback&response_type=code&state=1">
    <img style="width: 50px;height: 18px;margin-top: 35px;" src="/static/login/JD_img/gitee.png"/>
  </a>
</li>

(2)社交服务OAuth2Controller来处理应用回调地址/callback请求

分流程:(其实就只有三行代码是要自己配置的(OAuth2Controller的gitee的42-44行),其他的基本上是固定的)

1>封装AccessTokenDTO对象然后发给码云服务器,如果AccessTokenDTO对象正确的话就返回一个通行令牌(其实准确来说是用户授权后会返回一个code,然后通过code来去找码云服务器获取到一个通行令牌,最后通过这个通行令牌去找码云服务器要这个用户在gitee上公开的资料信息)

2>获取到了access_token通行令牌,转为通用gitee社交登录GiteeUser对象

3>远程调用会员服务来进行社交登录

package com.saodai.saodaimall.auth.controller;

import com.alibaba.fastjson.TypeReference;
import com.saodai.common.utils.R;
import com.saodai.common.vo.MemberResponseVo;
import com.saodai.saodaimall.auth.component.GitheeProvider;
import com.saodai.saodaimall.auth.feign.MemberFeignService;
import com.saodai.saodaimall.auth.vo.AccessTokenDTO;
import com.saodai.saodaimall.auth.vo.GiteeUser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

import javax.servlet.http.HttpSession;

import static com.saodai.common.constant.AuthServerConstant.LOGIN_USER;


/**
* 社交第三方授权登录
**/

@Slf4j
@Controller
public class OAuth2Controller {
    
    @Autowired
    private MemberFeignService memberFeignService;
    @Autowired
    private AccessTokenDTO accessTokenDTO;
    @Autowired
    private GitheeProvider githeeProvider;
    
    @GetMapping(value = "/callback")
    // /callback?code=e867a1f4575d4a6161e3249423a0403898253bc593e4b031a8771739ee6769f5&state=1
    public String gitee(@RequestParam(name = "code") String code,@RequestParam(name = "state") String state, HttpSession session) throws Exception {
        System.out.println(code);
        //下面三行代码都是自己应用的值,可以在gitee的第三方应用中看到对应的值
        accessTokenDTO.setClient_id("32459f971ce6d89cfb9f70899525455d0653cb804f16b38a304e3447dc97d673");
        accessTokenDTO.setClient_secret("f3046c911c03cadcded986062708150d4232af3ca6aef0259e5a0198d2c15ba5");
        accessTokenDTO.setRedirect_uri("http://auth.saodaimall.com/callback");
        accessTokenDTO.setCode(code);
        accessTokenDTO.setState(state);
        String accessToken = githeeProvider.getAccessToken(accessTokenDTO);
        //2、处理
        if (!StringUtils.isEmpty(accessToken)) {
            //获取到了access_token,转为通用gitee社交登录对象
            GiteeUser giteeUser = githeeProvider.getGiteeUser(accessToken);
            //知道了哪个社交用户
            //1)、当前用户如果是第一次进网站,自动注册进来(为当前社交用户生成一个会员信息,以后这个社交账号就对应指定的会员)
            //登录或者注册这个社交用户
            //调用远程服务
            R oauthLogin = memberFeignService.oauthLogin(giteeUser);
            if (oauthLogin.getCode() == 0) {
                MemberResponseVo memberResponseVo = oauthLogin.getData("data", new TypeReference<MemberResponseVo>() {});
                log.info("登录成功:用户信息:{}",memberResponseVo.toString());
                
                //1、第一次使用session,命令浏览器保存卡号,JSESSIONID这个cookie
                //以后浏览器访问哪个网站就会带上这个网站的cookie
                //TODO 1、默认发的令牌。当前域(解决子域session共享问题)
                //TODO 2、使用JSON的序列化方式来序列化对象到Redis中
                session.setAttribute(LOGIN_USER,memberResponseVo);
                
                //2、登录成功跳回首页
                return "redirect:http://saodaimall.com";
            } else {
                
                return "redirect:http://auth.saodaimall.com/login.html";
            }
            
        } else {
            return "redirect:http://auth.saodaimall.com/login.html";
        }
        
    }
    
}
package com.saodai.saodaimall.auth.vo;


/**
 * AccessTokenDTO对象封装(gitee社交登录令牌)
 */

import org.springframework.stereotype.Component;

@Component
public class AccessTokenDTO {
    private String client_id;
    private String client_secret;
    private String code;
    private String redirect_uri;
    private String state;

    public String getClient_id() {
        return client_id;
    }

    public void setClient_id(String client_id) {
        this.client_id = client_id;
    }

    public String getClient_secret() {
        return client_secret;
    }

    public void setClient_secret(String client_secret) {
        this.client_secret = client_secret;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getRedirect_uri() {
        return redirect_uri;
    }

    public void setRedirect_uri(String redirect_uri) {
        this.redirect_uri = redirect_uri;
    }

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }
}
package com.saodai.saodaimall.auth.component;


import com.alibaba.fastjson.JSON;
import com.saodai.saodaimall.auth.vo.AccessTokenDTO;
import com.saodai.saodaimall.auth.vo.GiteeUser;
import okhttp3.*;
import org.springframework.stereotype.Component;

import java.io.IOException;

/**
 * 请求码云服务器
 */
@Component
public class GitheeProvider {

    //发起post请求获取AccessToken
    public String getAccessToken(AccessTokenDTO accessTokenDTO){
        MediaType mediaType= MediaType.get("application/json; charset=utf-8");
        OkHttpClient client = new OkHttpClient();
        RequestBody body = RequestBody.create(mediaType, JSON.toJSONString(accessTokenDTO));
        Request request = new Request.Builder()
                .url("https://gitee.com/oauth/token?grant_type=authorization_code&code="+accessTokenDTO.getCode()+
                        "&client_id="+accessTokenDTO.getClient_id()+"&redirect_uri="+accessTokenDTO.getRedirect_uri()+
                        "&client_secret="+accessTokenDTO.getClient_secret())
                .post(body)
                .build();
        try (Response response = client.newCall(request).execute()) {
            String string = response.body().string();
            System.out.println(string);
            String str1 = string.split(":")[1];
            String str2 = str1.split("\"")[1];
            return str2;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    //发起get请求返回GitUser对象,
    public GiteeUser getGiteeUser(String token){
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
                .url("https://gitee.com/api/v5/user?access_token="+token)
                .build();
        try (Response response = client.newCall(request).execute()) {
            String string=response.body().string();
            GiteeUser giteeUser = JSON.parseObject(string, GiteeUser.class);
            return giteeUser;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}
package com.saodai.saodaimall.auth.vo;

import lombok.Data;

/**
 * GiteeUser对象封装(社交登录的gitee对象)
 */
@Data
public class GiteeUser {
    //gitee用户名称
    private String name;
    //gitee用户id
    private String id;
    //gitee用户自我介绍
    private String bio;


}

(3)远程调用会员服务来进行社交登录

  /**
     * 社交登录
     * @param giteeUser
     * @return
     * @throws Exception
     */
  @PostMapping(value = "/oauth2/login")
    public R oauthLogin(@RequestBody GiteeUser giteeUser) throws Exception {

        MemberEntity memberEntity = memberService.login(giteeUser);

        if (memberEntity != null) {
            return R.ok().setData(memberEntity);
        } else {
            return R.error(BizCodeEnum.LOGINACCT_PASSWORD_EXCEPTION.getCode(),BizCodeEnum.LOGINACCT_PASSWORD_EXCEPTION.getMessage());
        }
    }
    /**
     * 社交登录
     * @param giteeUser
     * @return
     * @throws Exception
     */
    @Override
    public MemberEntity login(GiteeUser giteeUser) throws Exception {

        //获取gitee用户唯一id
        String giteeUserId = giteeUser.getId();

        //1、判断当前社交用户是否已经登录过系统
        MemberEntity memberEntity = this.baseMapper.selectOne(new QueryWrapper<MemberEntity>().eq("social_id", giteeUserId));
        //这个用户已经注册过
        if (memberEntity != null) {
            return memberEntity;
        } else {
            //2、没有查到当前社交用户对应的记录我们就需要注册一个
            MemberEntity register = new MemberEntity();
            //社交gitee登录的id作为会员id
            register.setId(Long.valueOf(giteeUserId));
            register.setSocialName(giteeUser.getName());
            register.setUsername(giteeUser.getName());
            register.setNickname(giteeUser.getName());
            register.setCreateTime(new Date());
            register.setSocialBio(giteeUser.getBio());
            register.setSocialId(giteeUserId);
            //把用户信息插入到数据库中
            this.baseMapper.insert(register);
            return register;

        }

    }
package com.saodai.saodaimall.member.entity;

import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.io.Serializable;
import java.util.Date;

/**
 * 会员
 */
@Data
@TableName("ums_member")
public class MemberEntity implements Serializable {
	private static final long serialVersionUID = 1L;

	/**
	 * id
	 */
	@TableId
	private Long id;
	/**
	 * 会员等级id
	 */
	private Long levelId;
	/**
	 * 用户名
	 */
	private String username;
	/**
	 * 密码
	 */
	private String password;
	/**
	 * 昵称
	 */
	private String nickname;
	/**
	 * 手机号码
	 */
	private String mobile;
	/**
	 * 邮箱
	 */
	private String email;
	/**
	 * 头像
	 */
	private String header;
	/**
	 * 性别
	 */
	private Integer gender;
	/**
	 * 生日
	 */
	private Date birth;
	/**
	 * 所在城市
	 */
	private String city;
	/**
	 * 职业
	 */
	private String job;
	/**
	 * 个性签名
	 */
	private String sign;
	/**
	 * 用户来源
	 */
	private Integer sourceType;
	/**
	 * 积分
	 */
	private Integer integration;
	/**
	 * 成长值
	 */
	private Integer growth;
	/**
	 * 启用状态
	 */
	private Integer status;
	/**
	 * 注册时间
	 */
	private Date createTime;

	/**
	 * 社交登录用户的ID
	 */
	private String socialId;

	/**
	 * 社交登录用户的名称
	 */
	private String socialName;

	/**
	 * 社交登录用户的自我介绍
	 */
	private String socialBio;

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/62110.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【负荷预测】基于双向LSTM模型进行电力需求预测(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️❤️&#x1f4a5;&#x1f4a5;&#x1f4a5; &#x1f389;作者研究&#xff1a;&#x1f3c5;&#x1f3c5;&#x1f3c5;主要研究方向是电力系统和智能算法、机器学…

html大学生网站开发实践作业:传统文化网页设计题材【绒花6页】HTML+CSS+JavaScript

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

图解curator如何实现zookeeper分布式锁

文章目录一、前言二、curator分布式锁种类三、Zookeeper分布式锁概述1、Zookeeper分布式锁实现思路2、Zookeeper分布式锁解决的问题3、Zookeeper分布式锁优缺点&#xff1f;四、InterProcessMute实现分布式锁原理1、加锁流程&#xff08;acquire()方法&#xff09;0&#xff09…

【消息中间件】为什么选择RocketMQ及SpringBoot整合使用案例

目录 一、为什么选择RocketMQ 1、为什么是为什么选择RocketMQ 2、RocketMQ、ActiveMQ和Kafka之间的比较 2.1、对比1 2.2、对比2&#xff0c;接着上表 二、使用案例 1、引入依赖 2、编写启动类 3、编写application.yml配置文件 4、创建rocketmq文件夹 4.1、创建生产者…

YOLO算法创新改进系列项目汇总(入门级教程指南)

&#x1f680;&#x1f680;&#x1f680;——YOLO算法创新改进系列项目汇总——&#x1f384;&#x1f384;&#x1f384; &#x1f680; YOLO算法创新改进系列项目汇总 &#x1f384;&#x1f388; &#x1f340; 改进YOLOv5/YOLOv7——魔改YOLOv5/YOLOv7提升检测精度&#x…

Redis原理篇——内存回收

Redis是基于内存存储的&#xff0c;性能强。Redis的性能瓶颈也在于内存&#xff0c;但是单节点内存不宜过大&#xff0c;会影响持久化或主从同步性能。 通过配置文件来设置Redis的最大内存&#xff1a; 一、过期策略 Redis是键值类型的数据库&#xff0c;所有的key和value保…

MySQL高级语句(二)

一、VIEW&#xff08;视图&#xff09; 1、 概念 可以被当作是虚拟表或存储查询 视图跟表格的不同是&#xff0c;表格中有实际储存资料&#xff0c;而视图是建立在表格之上的一个架构&#xff0c;它本身并不实际储存资料。 临时表在用户退出或同数据库的连接断开后就自动消…

数据结构—链表

文章目录链表&#xff08;头插法、尾插法、单链表反转&#xff09;二分查找算法&#xff1a;哈夫曼编码构建链表insert()创建链表&#x1f447;【1】尾插法【2】头插法【3】遍历输出链表【4】输出链表的长度【5】查找链表上是否有该元素【6】指定位置插入数据链表经典面试题【1…

一文进阶什么是负载均衡,通俗易懂的全文解析

文章目录1 写在前面2 引言3 概念4 分类4.1 根据载体类型分类4.3 根据地域范围分类4.4 根据 OSI 网络模型分类4.5 对比(四层和七层)5 算法与实现5.1 随机法(Random)5.2 轮询法(Round Robin)5.3 加权轮询法(Weighted Round Robin)5.4 加权随机法(Weighted Random)5.5 最快响应速度…

从JDK8到JDK18,Java垃圾回收的详细解答

经历了数千次改进&#xff0c;Java 的垃圾回收在吞吐量、延迟和内存大小方面有了巨大的进步。 2014 年3 月 JDK 8 发布&#xff0c;自那以来 JDK 又连续发布了许多版本&#xff0c;直到今日的 JDK 18 是 Java 的第十个版本。借此机会&#xff0c;我们来回顾一下 HotSpot JVM 的…

python隶属关系图模型:基于模型的网络中密集重叠社区检测方法

隶属关系图模型 是一种生成模型&#xff0c;可通过社区联系产生网络。下图描述了一个社区隶属关系图和网络的示例&#xff08;图1&#xff09;。最近我们被客户要求撰写关于社区检测的研究报告&#xff0c;包括一些图形和统计输出。 图1.左&#xff1a;社区关系图&#xff08;圆…

ARM 汇编写启动代码之开 iCache

一、什么是 cache&#xff0c;有什么用 cache是一种内存&#xff0c;叫高速缓存。 从容量来说&#xff1a;CPU < 寄存器 < cache < DDR 从速度来说&#xff1a;CPU > 寄存器 > cache > DDRcache 的存在&#xff0c;是因为寄存器和 ddr 之间速度差异太大&a…

亚马逊云科技——云原生主题容器入门笔记

嗨&#xff0c;大家好&#xff0c;我是异星球的小怪同志 一个想法有点乱七八糟的小怪 如果觉得对你有帮助&#xff0c;请支持一波。 希望未来可以一起学习交流。 目录 一、容器入门课程 二、容器入门课堂笔记 1.容器背后的发展历史 2.区分容器与逻辑服务器的虚拟机 3.容…

号称 Java 圣经,Github 上爆火的 1058 页 JVM 全栈小册到底有什么魅力?

对于 JVM&#xff0c;我想大部分小伙伴都是要面试了才会去学&#xff0c;其余时间基本不会去看&#xff08;掐指一算&#xff0c;你们书架上面的深入理解 Java 虚拟机第三版应该都一层灰了吧【手动狗头】&#xff09;。但值得一说的是&#xff0c;当你工作多年之后&#xff0c;…

谷歌要完,百度也危了

文 | 天于刀刀当我们在抱怨搜索引擎的时候我们具体在说些什么&#xff1f;也许是饱受诟病的广告&#xff1f;或者是不合理的网页排序&#xff1f;又或是一种最直观的感觉——不好使。但是从来没有人抱怨过搜索引擎这一个模式。尽管这些年&#xff0c;也诞生一些诸如Magi这样让人…

Nginx配置实例-负载均衡

随着互联网信息的爆炸性增长&#xff0c;负载均衡&#xff08;load balance&#xff09;已经不再是一个很陌生的话题&#xff0c; 顾名思义&#xff0c;负载均衡即是将负载分摊到不同的服务单元&#xff0c;既保证服务的可用性&#xff0c;又保证响应 足够快&#xff0c;给用户…

Nginx安装搭建和环境准备教程(Ubantu)

本文以Ubantu18.08为例&#xff1a; 首先进入虚拟机中升级apt-get&#xff1a; apt-get update nginx进行安装&#xff1a; apt install nginx 使用命令查看nginx是否启动&#xff1a; systemctl status nginx nginx已经启动&#xff0c;可以到前端页面去访问是否真的已经启…

Vue项目中使用AntV G6绘制自适应图谱

Vue项目中使用AntV G6绘制自适应图谱 一、需求 需求1&#xff1a;Vue3.x项目下使用AntV G6绘制图谱 需求2&#xff1a;图谱节点为两个IP地址&#xff0c;节点间存在多条连线情况 需求3&#xff1a;鼠标悬浮到节点上方时&#xff0c;高亮当前节点并出现tooltip气泡提示&#…

Blackmagic黑魔法摄像机braw视频文件修复方法

Blackmagic是全球知名的影视级产品供应商&#xff0c;其高清摄像机是国内外各种剧组的最爱。Blackmagic的新产品目前使用braw格式&#xff0c;其编码采用自定义的raw编码&#xff0c;视频的效果和阿莱不相上下。近期我们处理了一例braw损坏无法播放的问题&#xff0c;我们来看看…

网络编程入门

什 么 是 网 络 编 程 网络编程的本质是多台计算机之间的数据交换。 数据传递本身没有多大的难度&#xff0c;不就是把一个设备中的数据发送给其他设备&#xff0c;然后接受另外一个设备反馈的数据。 现在的网络编程基本上都是基于请求/响应方式的&#xff0c;也就是一个设备发…