认证服务分为两种,一种是社交登录,一种是单点登录,都交给认证中心(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("存在相同的手机号"); } }