-
- 用户登录
- 构建页面:Login.vue
- 用户登录
- 步骤一:创建Login.vue
- 步骤二:绘制通用模块
<template>
<div>
<TopNav></TopNav>
<div style="clear:both;"></div>
<HeaderLogo></HeaderLogo>
<div style="clear:both;"></div>
<!-- 正文 -->
<div style="clear:both;"></div>
<Footer></Footer>
</div>
</template>
<script>
import TopNav from '../components/TopNav'
import HeaderLogo from '../components/HeaderLogo'
import Footer from '../components/Footer'
export default {
head: {
title: '用户登录',
link: [
{rel:'stylesheet',href:'style/login.css'}
],
script: [
]
},
components : {
TopNav,
HeaderLogo,
Footer
},
}
</script>
<style>
</style>
- 步骤三:绘制登录表单
<template>
<div>
<TopNav></TopNav>
<div style="clear:both;"></div>
<HeaderLogo></HeaderLogo>
<div style="clear:both;"></div>
<!-- 正文 -->
<!-- 登录主体部分start -->
<div class="login w990 bc mt10">
<div class="login_hd">
<h2>用户登录</h2>
<b></b>
</div>
<div class="login_bd">
<div class="login_form fl">
<form action="" method="post">
<ul>
<li>
<label for="">用户名:</label>
<input type="text" class="txt" name="username" />
</li>
<li>
<label for="">密码:</label>
<input type="password" class="txt" name="password" />
<a href="">忘记密码?</a>
</li>
<li class="checkcode">
<label for="">验证码:</label>
<input type="text" name="checkcode" />
<img src="images/checkcode1.jpg" alt="" />
<span>看不清?<a href="">换一张</a></span>
</li>
<li>
<label for=""> </label>
<input type="checkbox" class="chb" /> 保存登录信息
</li>
<li>
<label for=""> </label>
<input type="submit" value="" class="login_btn" />
</li>
</ul>
</form>
<div class="coagent mt15">
<dl>
<dt>使用合作网站登录商城:</dt>
<dd class="qq"><a href=""><span></span>QQ</a></dd>
<dd class="weibo"><a href=""><span></span>新浪微博</a></dd>
<dd class="yi"><a href=""><span></span>网易</a></dd>
<dd class="renren"><a href=""><span></span>人人</a></dd>
<dd class="qihu"><a href=""><span></span>奇虎360</a></dd>
<dd class=""><a href=""><span></span>百度</a></dd>
<dd class="douban"><a href=""><span></span>豆瓣</a></dd>
</dl>
</div>
</div>
<div class="guide fl">
<h3>还不是商城用户</h3>
<p>现在免费注册成为商城用户,便能立刻享受便宜又放心的购物乐趣,心动不如行动,赶紧加入吧!</p>
<a href="regist.html" class="reg_btn">免费注册 >></a>
</div>
</div>
</div>
<!-- 登录主体部分end -->
<div style="clear:both;"></div>
<Footer></Footer>
</div>
</template>
<script>
import TopNav from '../components/TopNav'
import HeaderLogo from '../components/HeaderLogo'
import Footer from '../components/Footer'
export default {
head: {
title: '用户登录',
link: [
{rel:'stylesheet',href:'style/login.css'}
],
script: [
]
},
components : {
TopNav,
HeaderLogo,
Footer
},
}
</script>
<style>
</style>
-
-
- 分析
-
-
-
- 验证码:接口
-
http://localhost:10010/web-service/verifycode?username=jack
-
-
- 验证码:生成与显示
-
- 步骤一:后端生产验证码,并将用户保存Redis
- 存放redis中验证码key格式:"login" + 用户名
package com.czxy.changgou4.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.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;
/**
* @author 桐叔
* @email liangtong@itcast.cn
*/
@Controller
@RequestMapping("/verifycode")
public class VerifyCodeController {
@Resource
private StringRedisTemplate stringRedisTemplate;
@GetMapping
public void verifyCode(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 );
}
//将验证码存放到redis
stringRedisTemplate.opsForValue().set( "login" + username , sb.toString() , 1 , TimeUnit.HOURS);
// 生成随机干扰线
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());
}
}
- 步骤二:点击“换一张”显示验证码
- 默认不显示验证码
- 点击“换一张”获得验证码
<template>
<div>
<TopNav></TopNav>
<div style="clear:both;"></div>
<HeaderLogo></HeaderLogo>
<div style="clear:both;"></div>
<!-- 正文 -->
<!-- 登录主体部分start -->
<div class="login w990 bc mt10">
<div class="login_hd">
<h2>用户登录</h2>
<b></b>
</div>
<div class="login_bd">
<div class="login_form fl">
<form action="" method="post">
<ul>
<li>
<label for="">用户名:</label>
<input type="text" class="txt" name="username" v-model="user.username" />
</li>
<li>
<label for="">密码:</label>
<input type="password" class="txt" name="password" v-model="user.password" />
<a href="">忘记密码?</a>
</li>
<li class="checkcode">
<label for="">验证码:</label>
<input type="text" name="checkcode" />
<!-- <img src="images/checkcode1.jpg" alt="" /> -->
<img :src="imgSrc" alt="" />
<span>看不清?<a href="" @click.prevent="changeVerifyCode">换一张</a></span>
</li>
<li v-if="errorMsg != ''">
<label for=""> </label>
<span style="color: #ff5b5b">{{errorMsg}}</span>
</li>
<li>
<label for=""> </label>
<input type="checkbox" class="chb" /> 保存登录信息
</li>
<li>
<label for=""> </label>
<input type="submit" value="" class="login_btn" />
</li>
</ul>
</form>
<div class="coagent mt15">
<dl>
<dt>使用合作网站登录商城:</dt>
<dd class="qq"><a href=""><span></span>QQ</a></dd>
<dd class="weibo"><a href=""><span></span>新浪微博</a></dd>
<dd class="yi"><a href=""><span></span>网易</a></dd>
<dd class="renren"><a href=""><span></span>人人</a></dd>
<dd class="qihu"><a href=""><span></span>奇虎360</a></dd>
<dd class=""><a href=""><span></span>百度</a></dd>
<dd class="douban"><a href=""><span></span>豆瓣</a></dd>
</dl>
</div>
</div>
<div class="guide fl">
<h3>还不是商城用户</h3>
<p>现在免费注册成为商城用户,便能立刻享受便宜又放心的购物乐趣,心动不如行动,赶紧加入吧!</p>
<a href="regist.html" class="reg_btn">免费注册 >></a>
</div>
</div>
</div>
<!-- 登录主体部分end -->
<div style="clear:both;"></div>
<Footer></Footer>
</div>
</template>
<script>
import TopNav from '../components/TopNav'
import HeaderLogo from '../components/HeaderLogo'
import Footer from '../components/Footer'
export default {
head: {
title: '用户登录',
link: [
{rel:'stylesheet',href:'style/login.css'}
],
script: [
]
},
components : {
TopNav,
HeaderLogo,
Footer
},
data() {
return {
imgSrc:'',
errorMsg: '',
user: {
}
}
},
methods: {
changeVerifyCode() {
if( this.user.username ) {
//this.$axios.defaults.baseURL
this.imgSrc = `http://localhost:10010/web-service/verifycode?t=${new Date().getTime()}&username=${this.user.username }`
} else {
this.errorMsg = '用户名不能为空'
}
}
},
watch: {
'user' : {
handler(v) {
if(v) {
//如果user数据发生改变,修改提示信息
this.errorMsg = ''
}
},
deep: true
}
},
}
</script>
<style>
</style>
-
-
- 通过用户名查询:接口
-
POST http://localhost:10010/web-service/user/findByUsername
{
"username":"jack"
}
-
-
- 通过用户名查询:实现
-
- 修改UserController,添加 findByUsername函数
/**
* 通过用户名查询
* @param user
* @return 返回用户对象
*/
@PostMapping("/findByUsername")
public User findByUsername(@RequestBody User user){
//查询用户
User findUser = userService.findByUsername( user.getUsername() );
return findUser;
}
-
-
- 认证服务:构建项目(changgou4-service-auth)
-
- 步骤一:构建项目
- 步骤二:创建pom.xml文件
<dependencies>
<!--自定义项目-->
<dependency>
<groupId>com.czxy.changgou</groupId>
<artifactId>changgou4_common_auth</artifactId>
</dependency>
<!--web起步依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- nacos 客户端 -->
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
</dependency>
<!-- nacos 服务发现 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<!--swagger2-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
</dependency>
<!-- feign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
- 步骤三:创建yml文件
server:
port: 8085
spring:
application:
name: auth-service
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 #nacos服务地址
sc:
jwt:
secret: sc@Login(Auth}*^31)&czxy% # 登录校验的密钥
pubKeyPath: D:/rsa/rsa.pub # 公钥地址
priKeyPath: D:/rsa/rsa.pri # 私钥地址
expire: 360 # 过期时间,单位分钟
- 步骤四:配置启动类
package com.czxy.changgou4;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* @author 桐叔
* @email liangtong@itcast.cn
*/
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class CGAuthServiceApplication {
public static void main(String[] args) {
SpringApplication.run(CGAuthServiceApplication.class, args);
}
}
- 步骤五:配置类
-
-
- 认证服务:用户登录后端
-
- 步骤一:创建AuthUser 封装对象(与User比较,缺数据库相关注解)
package com.czxy.changgou4.domain;
import lombok.Data;
import java.util.Date;
/**
* @author 桐叔
* @email liangtong@itcast.cn
*/
@Data
public class AuthUser {
private Long id;
private String username;
private String password;
private String face;
private Integer expriece;
private String email;
private String mobile;
private Date createdAt;
private Date updatedAt;
private String code;
private String password_confirm;
}
- 步骤二:创建UserFeign,完成远程用户查询功能
package com.czxy.changgou4.feign;
import com.czxy.changgou4.domain.AuthUser;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
/**
* @author 桐叔
* @email liangtong@itcast.cn
*/
@FeignClient(value = "web-service",path="/user")
public interface UserFeign {
@PostMapping("/findByUsername")
public AuthUser findByUsername(@RequestBody AuthUser user);
}
- 步骤三:创建AuthService接口,编写登录方法 login()
package com.czxy.changgou4.service;
import com.czxy.changgou4.domain.AuthUser;
/**
* @author 桐叔
* @email liangtong@itcast.cn
*/
public interface AuthService {
/**
* 用户登录
* @param user
* @return
*/
public AuthUser login(AuthUser user ) ;
}
- 步骤四:创建AuthService实现类,并通过BCrypt校验密码
package com.czxy.changgou4.service.impl;
import com.czxy.changgou4.domain.AuthUser;
import com.czxy.changgou4.feign.UserFeign;
import com.czxy.changgou4.service.AuthService;
import com.czxy.changgou4.utils.BCrypt;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* @author 桐叔
* @email liangtong@itcast.cn
*/
@Service
public class AuthServiceImpl implements AuthService {
@Resource
private UserFeign userFeign;
/**
* 用户登录
* @param user
* @return
*/
public AuthUser login(AuthUser user ) {
//远程查询用户
AuthUser findUser = userFeign.findByUsername(user);
if(findUser == null) {
return null;
}
//校验密码是否正确
boolean checkpw = BCrypt.checkpw( user.getPassword(), findUser.getPassword());
if(checkpw){
return findUser;
}
return null;
}
}
- 步骤五:创建AuthController,添加login方法
- redis中登录验证码和用户输入的验证码进行匹配
package com.czxy.changgou4.controller;
/**
* @author 桐叔
* @email liangtong@itcast.cn
*/
import com.czxy.changgou4.domain.AuthUser;
import com.czxy.changgou4.service.AuthService;
import com.czxy.changgou4.vo.BaseResult;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* Created by liangtong.
*/
@RestController
@RequestMapping("/auth")
public class AuthController {
@Resource
private AuthService authService;
@Resource
private StringRedisTemplate stringRedisTemplate;
@PostMapping("/login")
public BaseResult login(@RequestBody AuthUser user){
//校验验证码--使用后删除
String redisCode = stringRedisTemplate.opsForValue().get( "login" + user.getUsername() );
stringRedisTemplate.delete( "login" + user.getUsername() );
if(redisCode == null) {
return BaseResult.error("验证码无效");
}
if(! redisCode.equalsIgnoreCase(user.getCode())) {
return BaseResult.error("验证码错误");
}
//登录
AuthUser loginUser = authService.login(user);
if(loginUser != null ) {
return BaseResult.ok("登录成功").append("loginUser",loginUser);
} else {
return BaseResult.error("用户名或密码不匹配");
}
}
}
-
-
- 认证服务:用户登录前端
-
- 步骤一:修改apiclient.js,添加login函数
//登录
login : ( user )=> {
return axios.post('/auth-service/auth/login', user )
}
- 步骤二:修改Login.vue,给验证码绑定变量
<li class="checkcode">
<label for="">验证码:</label>
<input type="text" name="checkcode" v-model="user.code" />
<!-- <img src="images/checkcode1.jpg" alt="" /> -->
- 步骤三:修改Login.vue,给提交按钮绑定事件
<li>
<label for=""> </label>
<input type="submit" value="" @click.prevent="loginFn" class="login_btn" />
</li>
- 步骤四:编写loginFn完成登录功能
- 登录成功,跳转到首页
- 登录失败,给出提示
async loginFn() {
let { data } = await this.$request.login( this.user )
if( data.code == 20000) {
//成功
sessionStorage.setItem('user' , JSON.stringify(data.other.loginUser) )
//跳转到首页
this.$router.push('/')
} else {
this.errorMsg = data.message
}
}
- 步骤五:创建首页 ~/pages/index.vue,
<template>
<div>
<TopNav></TopNav>
</div>
</template>
<script>
import TopNav from '../components/TopNav'
export default {
head: {
title: '首页',
link: [
{rel:'stylesheet',href:'style/index.css'},
{rel:'stylesheet',href:'style/bottomnav.css'}
],
script: [
{ type: 'text/javascript', src: 'js/header.js' },
{ type: 'text/javascript', src: 'js/index.js' },
]
},
components : {
TopNav,
},
}
</script>
<style>
</style>
- 步骤六:重命名静态页面 ~/static/index.html 为 ~/static/home.html
-
-
- 修改 TopNav.vue 组件
-
- 完善导航条,根据vuex中的数据,显示不同内容
- 步骤一:创建 ~/store/index.js ,并编写vuex内容
export const state = () => ({
user: null
})
//通用设置
export const mutations = {
setData( state , obj) {
state[obj.key] = obj.value
}
}
- 步骤二:页面登录成功,将用户信息保存到vuex中
// 将用户信息保存到vuex中
this.$store.commit('setData', {key:'user',value: data.data })
- 步骤三:修改顶部导航TopNav.vue
- 从vuex中的数据
<template>
<!-- 顶部导航 start -->
<div class="topnav">
<div class="topnav_bd w990 bc">
<div class="topnav_left">
</div>
<div class="topnav_right fr">
<ul>
<li v-if="user != null">您好,{{user.username}} 欢迎来到畅购! <a href="" @click.prevent="logout">退出</a></li>
<li v-if="user != null" class="line">|</li>
<li v-if="user == null">[<a href="/login">登录</a>] [<a href="/register">免费注册</a>] </li>
<li v-if="user == null" class="line">|</li>
<li v-if="user != null">我的订单</li>
<li v-if="user != null" class="line">|</li>
<li>客户服务</li>
</ul>
</div>
</div>
</div>
<!-- 顶部导航 end -->
</template>
<script>
import {mapState,mapMutations} from 'vuex'
export default {
mounted() {
let userStr = sessionStorage.getItem('user')
if(userStr){
// 将string数据转换object,并填充到vuex中
this.setData({key:'user',value: JSON.parse(userStr)})
}
},
methods: {
logout() {
sessionStorage.removeItem('user')
sessionStorage.removeItem('token')
//设置vuex中的数据为空
this.setData({key:'user',value: null })
this.$router.push('/login')
},
...mapMutations(['setData'])
},
computed: {
...mapState(['user'])
},
}
</script>
<style>
</style>
-
-
- vuex刷新数据丢失
-
- 刷新操作:
- 点击刷新按钮
- 点击回退按钮
- 地址栏直接输入地址
- 现象:
- vuex在刷新操作后,数据丢失了
- 解决方案
- 方案1:不是公共组件:页面在pages目录下,可以nuxt.js提供 fetch进行操作。
- 方案2:是公共组件:组件在components目录下,借助第三方进行存储(cookie、localStorage、sessionStorage)
- 选择1:sessionStorage存放数据,如果vuex中没有,将sessionStorage同步过去。
- 选择2:vuex中actions模块就可以发送ajax,从而同步数据。
- 具体操作:
- 如果vuex中没有数据,使用sessionStorage的数据填充vuex。
- 修改TopNav.vue页面
<template>
<!-- 顶部导航 start -->
<div class="topnav">
<div class="topnav_bd w990 bc">
<div class="topnav_left">
</div>
<div class="topnav_right fr">
<ul>
<li v-if="user!=null">您好,{{user.username}}欢迎来到畅购!
</li>
<li v-if="user==null">
[<nuxt-link to="/Login">登录</nuxt-link>]
</li>
<li v-if="user==null">
[<nuxt-link to="/Register">免费注册</nuxt-link>]
</li>
<li v-if="user!=null">
[<nuxt-link to="/Login">退出</nuxt-link>]
</li>
<li v-if="user!=null" class="line">|</li>
<li v-if="user!=null">我的订单</li>
<li class="line">|</li>
<li>客户服务</li>
</ul>
</div>
</div>
</div>
<!-- 顶部导航 end -->
</template>
<script>
import {mapState, mapMutations} from 'vuex'
export default {
computed: { //计算属性整合vuex
// user() {
// return this.$store.state.user
// }
...mapState(['user'])
},
mounted() {
// 从sessionStorage获得信息,如果存在,直接添加vuex中
let userStr = sessionStorage.getItem('user')
if(userStr) {
let user = JSON.parse(userStr)
//将数据存放到vuex中
this.setData({key:'user',value: user })
}
},
methods: {
...mapMutations(['setData']),
},
}
</script>
<style>
</style>