4. 用户注册
4.0 整体流程
- 示意图
4.1 图片验证码
package com.czxy.zx.user.controller; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import javax.annotation.Resource; import javax.imageio.ImageIO; import javax.servlet.http.HttpServletResponse; import java.awt.*; import java.awt.image.BufferedImage; import java.io.IOException; import java.util.Random; import java.util.concurrent.TimeUnit; /** * Created by liangtong. */ @Controller @RequestMapping("/verifycode") public class VerifyCodeController { @Resource private StringRedisTemplate stringRedisTemplate; // /verifycode/jack @GetMapping("/{username}") public void verifyCode(@PathVariable("username") String username , HttpServletResponse response ) throws IOException { //字体只显示大写,去掉了1,0,i,o几个容易混淆的字符 String VERIFY_CODES = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ"; int IMG_WIDTH = 72; int IMG_HEIGTH = 27; Random random = new Random(); //创建图片 BufferedImage image = new BufferedImage(IMG_WIDTH, IMG_HEIGTH, BufferedImage.TYPE_INT_RGB); //画板 Graphics g = image.getGraphics(); //填充背景 g.setColor(Color.WHITE); g.fillRect(1,1,IMG_WIDTH-2,IMG_HEIGTH-2); g.setFont(new Font("楷体",Font.BOLD,25)); StringBuilder sb = new StringBuilder(); //写字 for(int i = 1 ; i <= 4 ; i ++){ //随机颜色 g.setColor(new Color(random.nextInt(255),random.nextInt(255),random.nextInt(255))); int len = random.nextInt(VERIFY_CODES.length()); String str = VERIFY_CODES.substring(len,len+1); sb.append(str); g.drawString(str, IMG_WIDTH / 6 * i , 22 ); } System.out.println("验证码:" + sb.toString()); // 将验证码保存redis中 stringRedisTemplate.opsForValue().set("register" + username , sb.toString() ); // 生成随机干扰线 for (int i = 0; i < 30; i++) { //随机颜色 g.setColor(new Color(random.nextInt(255),random.nextInt(255),random.nextInt(255))); int x = random.nextInt(IMG_WIDTH - 1); int y = random.nextInt(IMG_HEIGTH - 1); int x1 = random.nextInt(12) + 1; int y1 = random.nextInt(6) + 1; g.drawLine(x, y, x - x1, y - y1); } //响应到浏览器 ImageIO.write(image,"jpeg", response.getOutputStream()); } }
4.2 后端实现
4.2.1 完善EduUser
package com.czxy.zx.domain; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import io.swagger.annotations.ApiModel; import lombok.Data; import java.util.Date; /** * */ @Data //@TableName("edu_user") @ApiModel(value = "EduUser对象",description = "用户") public class EduUser { @TableId(value="id" , type = IdType.AUTO) private Integer id; //用户名 private String username; //用户密码 private String password; //电话 private String phone; //邮箱 private String email; //角色,多个值使用逗号分隔,例如:admin,editor private String roles; //创建时间 private Date created; //状态:0 未激活、1已激活 private String status; @TableField(exist = false) private String verifycode; @TableField(exist = false) private String repassword; }
4.2.2 service实现
- service 接口
package com.czxy.zx.user.service; import com.baomidou.mybatisplus.extension.service.IService; import com.czxy.zx.domain.EduUser; /** * @author 桐叔 * @email liangtong@itcast.cn */ public interface EduUserService extends IService<EduUser> { boolean register(EduUser eduUser); }
service实现类
package com.czxy.zx.user.service.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.czxy.zx.domain.EduUser; import com.czxy.zx.user.mapper.EduUserMapper; import com.czxy.zx.user.service.EduUserService; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.Date; /** * @author 桐叔 * @email liangtong@itcast.cn */ @Service @Transactional public class EduUserServiceImpl extends ServiceImpl<EduUserMapper, EduUser> implements EduUserService { @Override public boolean register(EduUser eduUser) { //1 校验,用户名存在不允许注册 QueryWrapper<EduUser> queryWrapper = new QueryWrapper(); queryWrapper.eq("username", eduUser.getUsername()); EduUser findUser = this.baseMapper.selectOne(queryWrapper); if(findUser != null) { throw new EduException("用户名已存在"); } //2 自动生成数据 eduUser.setCreated(new Date()); //创建时间 eduUser.setStatus("0"); //登录状态 //3 保存 int insert = this.baseMapper.insert(eduUser); //4 提示 return insert == 1; } }
4.3.3 controller
/** * 注册功能 * @param eduUser * @return */ @PostMapping("/register") public BaseResult register(@RequestBody EduUser eduUser) { //1.1 校验验证码 String redisVerifyCode = stringRedisTemplate.opsForValue().get("login" + eduUser.getUsername()); stringRedisTemplate.delete("login" + eduUser.getUsername()); if(redisVerifyCode == null) { return BaseResult.error("验证码无效"); } if(! redisVerifyCode.equalsIgnoreCase(eduUser.getVerifycode())) { return BaseResult.error("验证码错误"); } //1.2 密码校验 if(eduUser.getPassword() == null) { return BaseResult.error("密码不能为空"); } if(! eduUser.getPassword().equals(eduUser.getRepassword())) { return BaseResult.error("密码和确认密码不一致"); } //2 注册 boolean result = eduUserService.register(eduUser); //3 处理结果 if(result) { // 成功 // 3.1 生成UUID String uuid = UUID.randomUUID().toString().replace("-",""); // 3.2 生成激活路由 String url = "http://localhost:8080/active/"+eduUser.getUsername()+"/" + uuid; //访问前端 // 3.3 发送激活邮件 String text = eduUser.getUsername() + "您好:<br/>您使用本网站的激活程序,请<a href='"+url+"'>点击激活</a>"; // 3.4 发送邮件 UserEmail userEmail = new UserEmail(); userEmail.setUsername(eduUser.getUsername()); userEmail.setEmail(eduUser.getEmail()); userEmail.setText(text); String userEmailStr = JSONObject.toJSONString(userEmail); rabbitTemplate.convertAndSend(RabbitEmailConfig.QUEUE_NAME , userEmailStr); // 3.5 保存激活状态码 stringRedisTemplate.opsForValue().set("active" + eduUser.getUsername() , uuid , 5 , TimeUnit.MINUTES); return BaseResult.ok("注册成功"); } return BaseResult.ok("注册失败"); }
4.3 前端实现
4.3.1 显示页面
- 创建页面
@/views/edu/user/register.vue
<template> <div> 注册 </div> </template> <script> export default { } </script> <style> </style>
- 编写路由
{ path: '/register', component: () => import('@/views/edu/user/register'), hidden: true //登录成功后,左侧菜单中不显示 }
- 修改登录页面
<el-button class="thirdparty-button" type="primary" style="right:80px;" @click="showDialog=true"> 三方登录 </el-button> <el-button class="thirdparty-button" type="primary" @click="$router.push('/register')"> 注册 </el-button>
将注册连接添加到白名单
4.3.2 前端 api
export function register(user) { // 真实数据 return axios.post('/user-service/user/register',user); }
4.3.3 注册页面
<template> <div class="login-container"> <el-form ref="loginForm" :model="loginForm" class="login-form" > <div class="title-container"> <h3 class="title">注册表单</h3> </div> <el-form-item prop="username"> <span class="svg-container"> <svg-icon icon-class="user" /> </span> <el-input ref="username" v-model="loginForm.username" placeholder="用户名" type="text" tabindex="1" @blur="reload" /> </el-form-item> <el-tooltip v-model="capsTooltip" content="Caps lock is On" placement="right" manual> <el-form-item prop="password"> <span class="svg-container"> <svg-icon icon-class="password" /> </span> <el-input :key="passwordType" ref="password" v-model="loginForm.password" :type="passwordType" placeholder="密码" tabindex="2" autocomplete="on" @keyup.native="checkCapslock" @blur="capsTooltip = false" @keyup.enter.native="handleLogin" /> <span class="show-pwd" @click="showPwd"> <svg-icon :icon-class="passwordType === 'password' ? 'eye' : 'eye-open'" /> </span> </el-form-item> </el-tooltip> <el-tooltip v-model="recapsTooltip" content="Caps lock is On" placement="right" manual> <el-form-item prop="repassword"> <span class="svg-container"> <svg-icon icon-class="password" /> </span> <el-input :key="repasswordType" ref="repassword" v-model="loginForm.repassword" :type="repasswordType" placeholder="确认密码" tabindex="3" autocomplete="on" @keyup.native="recheckCapslock" @blur="recapsTooltip = false" /> <span class="show-pwd" @click="reshowPwd"> <svg-icon :icon-class="repasswordType === 'password' ? 'eye' : 'eye-open'" /> </span> </el-form-item> </el-tooltip> <!-- 手机 --> <el-form-item prop="phone"> <span class="svg-container"> <svg-icon icon-class="wechat" /> </span> <el-input ref="phone" v-model="loginForm.phone" placeholder="请输入手机号" name="phone" type="text" tabindex="4" /> </el-form-item> <!-- 邮箱 --> <el-form-item prop="email"> <span class="svg-container"> <svg-icon icon-class="email" /> </span> <el-input ref="email" v-model="loginForm.email" placeholder="请输入邮箱" name="email" type="text" tabindex="5" /> </el-form-item> <!-- 验证码 --> <el-form-item prop="verifycode"> <span class="svg-container"> <svg-icon icon-class="guide" /> </span> <el-input ref="verifycode" v-model="loginForm.verifycode" placeholder="请输入验证码" name="verifycode" type="text" tabindex="6" style="width:70%;" /> <img :src="rerifycodeImg" @click="reload" alt=""> </el-form-item> <el-button type="primary" style="width:100%;margin-bottom:30px;" @click.native.prevent="userRegister">注册</el-button> </el-form> </div> </template> <script> export default { data() { return { loginForm: { }, capsTooltip: false, passwordType: 'password', recapsTooltip: false, repasswordType: 'password', rerifycodeImg: '', } }, methods: { showPwd() { if (this.passwordType === 'password') { this.passwordType = '' } else { this.passwordType = 'password' } this.$nextTick(() => { this.$refs.password.focus() }) }, checkCapslock(e) { const { key } = e this.capsTooltip = key && key.length === 1 && (key >= 'A' && key <= 'Z') }, reshowPwd() { if (this.repasswordType === 'password') { this.repasswordType = '' } else { this.repasswordType = 'password' } this.$nextTick(() => { this.$refs.repassword.focus() }) }, recheckCapslock(e) { const { key } = e this.recapsTooltip = key && key.length === 1 && (key >= 'A' && key <= 'Z') }, reload() { // "路径?t=" + new Date() ,提供一个t变量,用于唯一标识每一次访问路径 this.rerifycodeImg = `http://localhost:10010/v2/user-service/verifycode/${this.loginForm.username}?t=` + new Date().getTime() }, userRegister() { } }, } </script> <style lang="scss"> /* 修复input 背景不协调 和光标变色 */ /* Detail see https://github.com/PanJiaChen/vue-element-admin/pull/927 */ $bg:#283443; $light_gray:#fff; $cursor: #fff; @supports (-webkit-mask: none) and (not (cater-color: $cursor)) { .login-container .el-input input { color: $cursor; } } /* reset element-ui css */ .login-container { .el-input { display: inline-block; height: 47px; width: 85%; input { background: transparent; border: 0px; -webkit-appearance: none; border-radius: 0px; padding: 12px 5px 12px 15px; color: $light_gray; height: 47px; caret-color: $cursor; &:-webkit-autofill { box-shadow: 0 0 0px 1000px $bg inset !important; -webkit-text-fill-color: $cursor !important; } } } .el-form-item { border: 1px solid rgba(255, 255, 255, 0.1); background: rgba(0, 0, 0, 0.1); border-radius: 5px; color: #454545; } } </style> <style lang="scss" scoped> $bg:#2d3a4b; $dark_gray:#889aa4; $light_gray:#eee; .login-container { min-height: 100%; width: 100%; background-color: $bg; overflow: hidden; .login-form { position: relative; width: 520px; max-width: 100%; padding: 160px 35px 0; margin: 0 auto; overflow: hidden; } .tips { font-size: 14px; color: #fff; margin-bottom: 10px; span { &:first-of-type { margin-right: 16px; } } } .svg-container { padding: 6px 5px 6px 15px; color: $dark_gray; vertical-align: middle; width: 30px; display: inline-block; } .title-container { position: relative; .title { font-size: 26px; color: $light_gray; margin: 0px auto 40px auto; text-align: center; font-weight: bold; } } .show-pwd { position: absolute; right: 10px; top: 7px; font-size: 16px; color: $dark_gray; cursor: pointer; user-select: none; } .thirdparty-button { position: absolute; right: 0; bottom: 6px; } @media only screen and (max-width: 470px) { .thirdparty-button { display: none; } } } </style>
- 获得
.env.development
文件中配置内容
reload() { //this.rerifycodeImg = `http://localhost:10010/v2/user-service/verifycode/${this.loginForm.username}?t=${new Date().getTime()}` this.rerifycodeImg = `${process.env.VUE_APP_BASE_API}/user-service/verifycode/${this.loginForm.username}?t=${new Date().getTime()}` }
4.3.4 注册功能
async userRegister() { let {message} = await register(this.loginForm) this.$message.success(message) //跳转到登录 this.$router.push('/login') }
5 整合JWT
5.0 分析
5.1 搭建环境
5.1.1 拷贝坐标
<!--JavaBean工具类,用于JavaBean数据封装--> <dependency> <groupId>commons-beanutils</groupId> <artifactId>commons-beanutils</artifactId> </dependency> <!--jwt工具--> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> </dependency> <!--joda 时间工具类 --> <dependency> <groupId>joda-time</groupId> <artifactId>joda-time</artifactId> </dependency>
5.1.2 复制yml配置
sc: jwt: secret: sc@Login(Auth}*^31)&czxy% # 登录校验的密钥 pubKeyPath: D:/rsa/rsa.pub # 公钥地址 priKeyPath: D:/rsa/rsa.pri # 私钥地址 expire: 360 # 过期时间,单位分钟
5.1.3 拷贝配合类
- 拷贝:JwtProperties
package com.czxy.zx.user.config; import com.czxy.zx.user.utils.RsaUtils; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; import javax.annotation.PostConstruct; import java.io.File; import java.security.PrivateKey; import java.security.PublicKey; /** * @author 桐叔 * @email liangtong@itcast.cn */ @Configuration @ConfigurationProperties(prefix = "sc.jwt") @Data public class JwtProperties { private String secret; private String pubKeyPath; private String priKeyPath; private Integer expire; private PublicKey publicKey; private PrivateKey privateKey; @PostConstruct //初始化方法注解 public void init() { try { File pubFile = new File(pubKeyPath); File priFile = new File(priKeyPath); if(!pubFile.exists() || ! priFile.exists()) { RsaUtils.generateKey(pubKeyPath,priKeyPath,secret); } // 获得公钥和私钥对象 this.publicKey = RsaUtils.getPublicKey(pubKeyPath); this.privateKey = RsaUtils.getPrivateKey(priKeyPath); } catch (Exception e) { e.printStackTrace(); } } }
5.1.4 拷贝工具类
- 拷贝工具类
5.1.5 生成秘钥(可选)
- 如果已经存在,此步省略
编写测试类,生成公钥和私钥
package com.czxy.zx.utils; /** * @author 桐叔 * @email liangtong@itcast.cn */ public class TestRsa { //公钥的位置 private static final String pubKeyPath = "D:\\rsa\\rsa.pub"; //私钥的位置 private static final String priKeyPath = "D:\\rsa\\rsa.pri"; public static void main(String[] args) throws Exception { RsaUtils.generateKey(pubKeyPath,priKeyPath,"1234"); } }