7、编写controller,根据手机号发送短信
@RestController @RequestMapping("/smsservice/sms") @CrossOrigin public class SmsApiController { @Autowired private RedisTemplate<String,String> redisTemplate; @Autowired private SmsService smsService; //阿里云发送短信 @GetMapping("sendMsgByAly/{phone}") public R sendMsgByAly(@PathVariable String phone){ //生成随机值,传递到阿里云进行发送 String code = RandomUtil.getSixBitRandom(); Map <String, Object> param = new HashMap <>(); param.put("code", code); //调用service进行短信发送 boolean isSend = smsService.sendMsgAly(param, phone); if(isSend){ return R.ok(); }else{ return R.error().message("短信发送失败"); } } //使用国阳云发送短信 @GetMapping("sendMsgByGyy/{phone}") public R sendMsgByGyy(@PathVariable String phone){ //从redis中获取验证码,如果获取则直接返回..==> 根据现实情况,用户可以多次发送. // String code = redisTemplate.opsForValue().get(phone); // if(!StringUtils.isEmpty(code)){ // return R.ok(); // } //2.如果redis获取不到,进行阿里云发送 //生成随机值,传递到阿里云进行发送 String code = RandomUtil.getSixBitRandom(); //调用service进行短信发送 boolean isSend = smsService.sendMsgGyy(code, phone); if(isSend){ //发送成功,把发送成功验证码放入到redis中 //设置有效时间 redisTemplate.opsForValue().set(phone, code, 30, TimeUnit.MINUTES); return R.ok(); }else{ return R.error().message("短信发送失败"); } } }
8、编写service
@Service public class SmsServiceImpl implements SmsService { //通过阿里云发送短信(由于未申请下来,所以就先写个方法.项目中用国阳云/邮箱进行代替) @Override public boolean sendMsgAly(Map <String, Object> param, String phone) { if(StringUtils.isEmpty(phone)) return false; DefaultProfile profile = DefaultProfile.getProfile("default", "LTAIq6nIPY09VROj", "FQ7UcixT9wEqMv9F35nORPqKr8XkTF"); IAcsClient client = new DefaultAcsClient(profile); CommonRequest request = new CommonRequest(); //request.setProtocol(ProtocolType.HTTPS); request.setMethod(MethodType.POST); request.setDomain("dysmsapi.aliyuncs.com"); request.setVersion("2017-05-25"); request.setAction("SendSms"); request.putQueryParameter("PhoneNumbers", phone); request.putQueryParameter("SignName", "我的谷粒在线教育网站"); request.putQueryParameter("TemplateCode", "templateCode"); request.putQueryParameter("TemplateParam", JSONObject.toJSONString(param)); try { CommonResponse response = client.getCommonResponse(request); System.out.println(response.getData()); return response.getHttpResponse().isSuccess(); } catch (Exception e) { e.printStackTrace(); return false; } } //使用国阳云发送短信 @Override public boolean sendMsgGyy(String code, String phone) { try { //真正的短信发送 用于上线 // SmsUtil.sendMessage(ConstantPropertiesUtil.APP_CODE,phone,code,"30",ConstantPropertiesUtil.SMS_SIGNID,ConstantPropertiesUtil.TEMPLATE_ID); //太贵了,先用邮箱代替.... MailUtils.sendMail("2422737092@qq.com", "[谷粒学院] 验证码: \n"+code+"您正在进行注册,若非本人操作,请勿泄露.30分钟内有效.", "谷粒学院验证码"); return true; }catch (Exception e){ e.printStackTrace(); return false; } } }
9、使用swagger进行测试
http://localhost:8005/swagger-ui.html
国阳云短信如何使用,可参考 如何利用阿里云市场购买并使用短信服务
六、用户登录注册接口【后端】
1、在service模块下创建子模块service_ucenter
2、使用代码生成器生成代码
(1)创建ucenter_member表
(2)利用代码生成器生成代码
3、配置application.properties
# 服务端口 server.port=8006 # 服务名 spring.application.name=service-ucenter # mysql数据库连接 spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/guli_edu spring.datasource.username=root spring.datasource.password=186259 spring.redis.host=192.168.174.128 spring.redis.port=6379 spring.redis.database= 0 spring.redis.timeout=1800000 spring.redis.lettuce.pool.max-active=20 spring.redis.lettuce.pool.max-wait=-1 #最大阻塞等待时间(负数表示没限制) spring.redis.lettuce.pool.max-idle=5 spring.redis.lettuce.pool.min-idle=0 #最小空闲 #nacos服务地址 spring.cloud.nacos.discovery.server-addr=192.168.174.128:8848 #返回json的全局时间格式 spring.jackson.date-format=yyyy-MM-dd HH:mm:ss spring.jackson.time-zone=GMT+8 #配置mapper xml文件的路径 mybatis-plus.mapper-locations=classpath:com/rg/ucenterservice/mapper/xml/*.xml #mybatis日志 mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
4、创建启动类
创建UcenterMemberApplication.java
@SpringBootApplication @ComponentScan("com.rg") @MapperScan("com.rg.ucenterservice.mapper") public class UcenterMemberApplication { public static void main(String[] args) { SpringApplication.run(UcenterMemberApplication.class, args); } }
5、创建Controller编写登录和注册方法
@RestController @RequestMapping("/ucenterservice/member") @CrossOrigin public class UcenterMemberController { @Autowired private UcenterMemberService memberService; //用户登录 @PostMapping("login") public R login(@RequestBody LoginVo loginVo){ //返回token,使用jwt生成 String token = memberService.login(loginVo); return R.ok().data("token",token); } //用户注册 @PostMapping("register") public R register(@RequestBody RegisterVo registerVo){ R result = memberService.register(registerVo); return result; } //根据token获取用户信息 @GetMapping("getMemberInfo") public R getMemberInfo(HttpServletRequest request){ String memberId = JwtUtils.getMemberIdByJwtToken(request); UcenterMember member = memberService.getById(memberId); return R.ok().data("userInfo", member); } }
6、创建service接口和实现类
@Service public class UcenterMemberServiceImpl extends ServiceImpl<UcenterMemberMapper, UcenterMember> implements UcenterMemberService { @Autowired private RedisTemplate<String,String> redisTemplate; //用户登录 @Override public String login(LoginVo loginVo) { String mobile = loginVo.getMobile(); String password = loginVo.getPassword(); //判断用户名密码,是否为空 if(StringUtils.isEmpty(mobile) || StringUtils.isEmpty(password)){ throw new GuLiException(20001, "用户名或密码不能为空!"); } //去数据库查询 QueryWrapper <UcenterMember> wrapper = new QueryWrapper <>(); wrapper.eq("mobile",mobile); UcenterMember ucenterMember = this.baseMapper.selectOne(wrapper); //判断UCenterMember是否为空 if(ucenterMember==null){//没有这个手机号 throw new GuLiException(20001,"用户名或密码错误!"); } //判断密码是否相等 if(!MD5Utils.md5(password).equals(ucenterMember.getPassword())){ throw new GuLiException(20001,"用户名或密码错误!"); } //判断是否被禁用 if(ucenterMember.getIsDisabled()){ throw new GuLiException(20001, "您的账号暂时被冻结,请联系管理员!"); } //登录成功 //生成token字符串,使用jwt工具类 String jwtToken = JwtUtils.getJwtToken(ucenterMember.getId(), ucenterMember.getMobile()); return jwtToken; } //用户注册 @Override public R register(RegisterVo registerVo) { String code = registerVo.getCode(); String mobile = registerVo.getMobile(); String nickname = registerVo.getNickname(); String password = registerVo.getPassword(); //判断是否为空 if(StringUtils.isEmpty(mobile) || StringUtils.isEmpty(password) || StringUtils.isEmpty(code) || StringUtils.isEmpty(nickname)){ return R.error().message("数据格式不正确!!"); } //判断验证码是否正确 String redisCode = redisTemplate.opsForValue().get(mobile); if(!code.equals(redisCode)){ return R.error().message("验证码无效或已过期,请重新发送验证码!"); } //判断手机号是否已经注册 QueryWrapper <UcenterMember> wrapper = new QueryWrapper <>(); wrapper.eq("mobile",mobile); Integer count = this.baseMapper.selectCount(wrapper); if(count > 0){ return R.error().message("手机号已被注册!"); } //判断昵称是否重复 wrapper = new QueryWrapper <UcenterMember>(); wrapper.eq("nickname",nickname); count = this.baseMapper.selectCount(wrapper); if(count > 0){ return R.error().message("昵称 昵称已被使用,换一个吧"); } //进行数据插入 UcenterMember member = new UcenterMember(); member.setMobile(mobile); member.setNickname(nickname); member.setPassword(MD5Utils.md5(password)); member.setIsDeleted(false);//用户不禁用 member.setAvatar("https://guli-photos.oss-cn-hangzhou.aliyuncs.com/FatDog.jpg"); this.baseMapper.insert(member); return R.ok(); } }
7、MD5工具类
public class MD5Utils { /** * 使用md5的算法进行加密 */ public static String md5(String plainText) { byte[] secretBytes = null; try { secretBytes = MessageDigest.getInstance("md5").digest( plainText.getBytes()); } catch (NoSuchAlgorithmException e) { throw new RuntimeException("没有md5这个算法!"); } String md5code = new BigInteger(1, secretBytes).toString(16);// 16进制数字 // 如果生成数字未满32位,需要前面补0 for (int i = 0; i < 32 - md5code.length(); i++) { md5code = "0" + md5code; } return md5code; } public static void main(String[] args) { System.out.println(md5("123456")); } }
8、使用swagger测试
http://localhost:8006/swagger-ui.html
七、用户登录注册【前端】
1、在Nuxt环境中安装插件
(1)安装element-ui 和 vue-qriously插件
npm install element-ui npm install vue-qriously
(2)修改配置文件 nuxt-swiper-plugin.js,使用插件
import Vue from 'vue' import VueAwesomeSwiper from 'vue-awesome-swiper/dist/ssr' import VueQriously from 'vue-qriously' import ElementUI from 'element-ui' //element-ui的全部组件 import 'element-ui/lib/theme-chalk/index.css'//element-ui的css Vue.use(ElementUI) //使用elementUI Vue.use(VueQriously) Vue.use(VueAwesomeSwiper)
2、用户注册功能前端整合
1、在api文件夹中定义接口
register.js
import request from '@/utils/request' export default { //发送短信 sendCode(phone){ return request({ url:`/smsservice/sms/sendMsgByGyy/${phone}`, method:`get` }) }, //注册的方法 registerMember(formItem){ return request({ url:`/ucenterservice/member/register`, method:`post`, data:formItem }) } }
2、在pages文件夹中创建注册页面,调用方法
(1)在layouts创建布局页面
sign.vue
<template> <div class="sign"> <!--标题--> <div class="logo"> <img src="~/assets/img/logo.png" alt="logo"> </div> <!--表单--> <nuxt/> </div> </template>
(2)创建注册页面
修改layouts文件夹里面default.vue页面,修改登录和注册超链接地址
在pages文件夹下,创建注册和登录页面
register.vue
<template> <div class="main"> <div class="title"> <a href="/login">登录</a> <span>·</span> <a class="active" href="/register">注册</a> </div> <div class="sign-up-container"> <el-form ref="userForm" :model="params"> <el-form-item class="input-prepend restyle" prop="nickname" :rules="[{ required: true, message: '请输入你的昵称', trigger: 'blur' }]"> <div> <el-input type="text" placeholder="你的昵称" v-model="params.nickname"/> <i class="iconfont icon-user"/> </div> </el-form-item> <el-form-item class="input-prepend restyle no-radius" prop="mobile" :rules="[{ required: true, message: '请输入手机号码', trigger: 'blur' },{validator: checkPhone, trigger: 'blur'}]"> <div> <el-input type="text" placeholder="手机号" v-model="params.mobile"/> <i class="iconfont icon-phone"/> </div> </el-form-item> <el-form-item class="input-prepend restyle no-radius" prop="code" :rules="[{ required: true, message: '请输入验证码', trigger: 'blur' }]"> <div style="width: 100%;display: block;float: left;position: relative"> <el-input type="text" placeholder="验证码" v-model="params.code"/> <i class="iconfont icon-phone"/> </div> <div class="btn" style="position:absolute;right: 0;top: 6px;width: 45%;"> <a href="javascript:" type="button" @click="getCodeFun()" :value="codeTest" style="border: none;background-color: none"> <el-button type="success" round size="medium" :disabled="!sending">{{codeTest}}</el-button> </a> </div> </el-form-item> <el-form-item class="input-prepend" prop="password" :rules="[{ required: true, message: '请输入密码', trigger: 'blur' }]"> <div> <el-input type="password" placeholder="设置密码" v-model="params.password"/> <i class="iconfont icon-password"/> </div> </el-form-item> <div class="btn"> <input type="button" class="sign-up-button" value="注册" @click="submitRegister('userForm')"> </div> <p class="sign-up-msg"> 点击 “注册” 即表示您同意并愿意遵守简书 <br> <a target="_blank" href="http://www.jianshu.com/p/c44d171298ce">用户协议</a> 和 <a target="_blank" href="http://www.jianshu.com/p/2ov8x3">隐私政策</a> 。 </p> </el-form> <!-- 更多注册方式 --> <div class="more-sign"> <h6>社交帐号直接注册</h6> <ul> <li><a id="weixin" class="weixin" target="_blank" href="http://huaan.free.idcfengye.com/api/ucenter/wx/login"><i class="iconfont icon-weixin"/></a></li> <li><a id="qq" class="qq" target="_blank" href="#"><i class="iconfont icon-qq"/></a></li> </ul> </div> </div> </div> </template> <script> import '~/assets/css/sign.css' import '~/assets/css/iconfont.css' import registerApi from '@/api/register' export default { layout: 'sign', data() { return { params: { mobile: '', code: '', nickname: '', password: '' }, sending: true, //是否发送验证码 second: 60, //倒计时间 codeTest: '获取验证码' } }, methods: { //注册提交的方法 submitRegister(formName){ this.$refs[formName].validate(valid=>{ if(!valid){ this.$message({ type:'error', message:'请填写正确的注册信息,再进行注册!' }) return false; } registerApi.registerMember(this.params).then(response=>{ console.log(response); //提示登录成功 this.$message({ type:'success', message:'注册成功' }) //跳转到登录页面 this.$router.push({path:'/login'}) }) //这里不用写catch了,因为外部进行了处理 }) }, getCodeFun(){ if(this.sending==false || this.params.mobile==""){ return; } registerApi.sendCode(this.params.mobile).then(response=>{ //禁止发送 this.sending = false //倒计时 this.timeDown() //提示验证码发送成功 this.$message({ type:'success', message:'验证码已发送' }) }) }, timeDown() { let result = setInterval(() => { --this.second; this.codeTest = "重新发送"+this.second+"(s)" if (this.second < 1) { clearInterval(result); this.sending = true; //this.disabled = false; this.second = 60; this.codeTest = "获取验证码" } }, 1000); }, checkPhone (rule, value, callback) { //debugger if (!(/^1[34578]\d{9}$/.test(value))) { return callback(new Error('手机号码格式不正确')) } return callback() } } } </script>
3、修改nginx配置文件的端口
4、启动服务进行测试
3、用户登录功能前端整合
1、在api文件夹中创建登录的js文件,定义接口
import request from '@/utils/request' export default { //登录的方法 submitLoginUser(userInfo){ return request({ url:`/ucenterservice/member/login`, method:'post', data:userInfo }) }, //根据token获取用户信息 getLoginUserInfo(){ return request({ url:`/ucenterservice/member/getMemberInfo`, method:'get' }) } }
2、在pages文件夹中创建登录页面,调用方法
(1)安装js-cookie插件
npm install js-cookie
(2)login.vue
<template> <div class="main"> <div class="title"> <a class="active" href="/login">登录</a> <span>·</span> <a href="/register">注册</a> </div> <div class="sign-up-container"> <el-form ref="userForm" :model="user"> <el-form-item class="input-prepend restyle" prop="mobile" :rules="[{ required: true, message: '请输入手机号码', trigger: 'blur' },{validator: checkPhone, trigger: 'blur'}]"> <div > <el-input type="text" placeholder="手机号" v-model="user.mobile"/> <i class="iconfont icon-phone" /> </div> </el-form-item> <el-form-item class="input-prepend" prop="password" :rules="[{ required: true, message: '请输入密码', trigger: 'blur' }]"> <div> <el-input type="password" placeholder="密码" v-model="user.password"/> <i class="iconfont icon-password"/> </div> </el-form-item> <div class="btn"> <input type="button" class="sign-in-button" value="登录" @click="submitLogin()"> </div> </el-form> <!-- 更多登录方式 --> <div class="more-sign"> <h6>社交帐号登录</h6> <ul> <li><a id="weixin" class="weixin" target="_blank" href="http://localhost:8160/api/ucenter/wx/login"><i class="iconfont icon-weixin"/></a></li> <li><a id="qq" class="qq" target="_blank" href="#"><i class="iconfont icon-qq"/></a></li> </ul> </div> </div> </div> </template> <script> import '~/assets/css/sign.css' import '~/assets/css/iconfont.css' import cookie from 'js-cookie' import loginApi from '@/api/login' export default { layout: 'sign', data () { return { user:{ mobile:'', password:'' }, loginInfo:{} } }, methods: { submitLogin(){ //第一步调用接口进行登录,返回token字符串 loginApi.submitLoginUser(this.user).then(response=>{ //第二步,获取token字符串存放到token中 cookie.set('guli_token',response.data.data.token,{domain:'localhost'}) //第四步 调用接口 根据token获取用户信息,为了首页面显示 loginApi.getLoginUserInfo().then(response=>{ this.loginInfo = response.data.data.userInfo //获取用户信息,存放到cookie里面 cookie.set("guli_ucenter",JSON.stringify(this.loginInfo),{domain:'localhost'}) //跳转页面 window.location.href = "/" }) }) }, checkPhone (rule, value, callback) { //debugger if (!(/^1[34578]\d{9}$/.test(value))) { return callback(new Error('手机号码格式不正确')) } return callback() } } } </script> <style> .el-form-item__error{ z-index: 9999999; } </style>
登录和登录成功后首页显示具体实现过程分析:
(1) 调用接口登录返回token字符串
(2) 把第一步返回token字符串放到cookie里面
(3) 创建拦截器:判断cookie里面是否有token字符串,如果有,把token字符串放到header(请求头中)
(4) 根据token值,调用接口,根据token获取用户信息,为了首页面显示。调用接口返回用户信息放到cookie里面
(5) 从首页面显示用户信息,从第四步cookie获取用户信息
3、在request.js添加拦截器,用于传递token信息
import axios from 'axios' import cookie from 'js-cookie' import { MessageBox, Message } from 'element-ui' // 创建axios实例 const service = axios.create({ baseURL: 'http://localhost:9001', // api的base_url timeout: 15000 // 请求超时时间 }) //第三步 创建拦截器 http request 拦截器 service.interceptors.request.use( config => { //debugger if (cookie.get('guli_token')) { config.headers['token'] = cookie.get('guli_token'); } return config }, err => { return Promise.reject(err); }) // http response 拦截器 service.interceptors.response.use( response => { //debugger if (response.data.code == 28004) { console.log("response.data.resultCode是28004") // 返回 错误代码-1 清除ticket信息并跳转到登录页面 //debugger window.location.href="/login" return }else{ if (response.data.code !== 20000) { //25000:订单支付中,不做任何提示 if(response.data.code != 25000) { Message({ message: response.data.message || 'error', type: 'error', duration: 5 * 1000 }) return Promise.reject('error') } } else { return response; } } }, error => { console.log('err' + error) // for debug Message({ message: error.message, type: 'error', duration: 5 * 1000 }) return Promise.reject(error.response) // 返回接口返回的错误信息 }); export default service
4、修改layouts中的default.vue页面
(1)显示登录之后的用户信息
<script> import '~/assets/css/reset.css' import '~/assets/css/theme.css' import '~/assets/css/global.css' import '~/assets/css/web.css' import cookie from 'js-cookie' import loginApi from '@/api/login' export default { data() { return { token: '', loginInfo: { id: '', age: '', avatar: '', mobile: '', nickname: '', sex: '' } } }, created() { //获取路径里面的token值 this.token = this.$route.query.token if(this.token){//判断路径中是否有token值 this.wxLogin() return; } this.showInfo() }, methods: { //从cookie中获取用户信息 showInfo(){ //从cookie获取用户信息 cookie中存放的都是str let userStr = cookie.get('guli_ucenter') console.log(userStr); if(userStr){ this.loginInfo = JSON.parse(userStr) } }, //退出 logout(){ //清空cookie值 cookie.set('guli_token','',{domain:'localhost'}) cookie.set('guli_ucenter','',{domain:'localhost'}) //回到首页面 window.location.href = "/" } } } </script>
(2)default.vue页面显示登录之后的用户信息
<ul class="h-r-login"> <li v-if="!loginInfo.id" id="no-login"> <a href="/login" title="登录"> <em class="icon18 login-icon"> </em> <span class="vam ml5">登录</span> </a> | <a href="/register" title="注册"> <span class="vam ml5">注册</span> </a> </li> <li v-if="loginInfo.id" id="is-login-one" class="mr10"> <a id="headerMsgCountId" href="#" title="消息"> <em class="icon18 news-icon"> </em> </a> <q class="red-point" style="display: none"> </q> </li> <li v-if="loginInfo.id" id="is-login-two" class="h-r-user"> <a href="/ucenter" title> <img :src="loginInfo.avatar" width="30" height="30" class="vam picImg" alt > <span id="userName" class="vam disIb">{{ loginInfo.nickname }}</span> </a> <a href="javascript:void(0);" title="退出" @click="logout()" class="ml5">退出</a> </li> <!-- /未登录显示第1 li;登录后显示第2,3 li --> </ul>
4、登录注册效果展示
- 用户填写注册信息,填写收到的短信验证码(目前先用邮箱代替),进行注册
- 注册表单校验功能
- 注册成功
- 用户登录