目录
- 用户模块需求分析
- 静态网站部署与调试
- 两种前端项目的部署
- 两种前端项目的调试(热部署)
- 创建静态web项目
- 注册分析与设计
- 分析需求
- 设计 界面设计(ui)设计表(后台) 流程设计(后台)
- 三范式
- 表设计
- 流程设计
- 相关技术准备
- 1.随机字符串(用StrUtils工具类)
- 2.springdata-redis
- 3.Md5技术
- 4.短信接口
- 发送短信验证码
- 前端页面准备
- 发送短信后台
- 搭建用户模块
- 单独写公共可用的发短信接口
- VerifycodeController
- VerifycodeServiceImpl
用户模块需求分析
用户注册
登录信息
基本信息
安全信息
用户登录
账号登录
扫码登录-自己app扫描页面
三方登录-使用三方app(微信,qq,支付宝等)
验证码登录-自己做 reids.set(logincode+phone,xxx)
用户管理
地址管理
收藏
商品收藏
店铺收藏
服务收藏
宠物收藏
。。。。
足迹
。。。。
静态网站部署与调试
两种前端项目的部署
vue-cli项目:pethome-admin(管理员的管理界面),用npm打包成静态项目后丢进Tomcat服务器跑
Css+div项目:pethome_web(用户访问门户网站),本身是静态项目可以直接丢进Tomcat服务器跑
两种前端项目的调试(热部署)
vue-cli项目可以npm run dev启动调试
传统静态项目需要安装 live-server服务器来调试,这个服务器很简单就是node的一个模块,
全局安装:npm install -g live-server,运行:live-server --port=80
创建静态web项目
拷贝amz_02_adp的two里项目资源到pethome-web目录下
复制home3.html到pethome-web根目录并改名为index.html作为首页
修改css和js引用路径
进入web子项目
全局安装:npm install -g live-server
启动调试:live-server --port=80
替换images图片
注册分析与设计
分析需求
注册分为:邮箱注册,和手机号注册,逻辑都是一样的,只是激活方式不一样。
手机号注册:
1)输入手机号
2)获取验证码并且输入
3)输入蜜马和确认蜜马
4)完成注册
设计 界面设计(ui)设计表(后台) 流程设计(后台)
三范式
1NF:列的原子性,不可再拆分
2NF:行唯一,
3NF:如果一张表的数据能够通过其他表推导出来,不应该单独设计,应该通过外键的方式关联查询出来。
反3NF:有的时候我们为了增强查询效率,会设计一些冗余字段,变多表查询为单表查询。
表设计
注意:以后只要在eployee或user操作时涉及到登录信息都要同步操作logininfo。比如添加员工也要在logininfo添加一条记录,注册用户也要在logininfo中添加。同理删除或修改也要同步。
流程设计
见文档
相关技术准备
1.随机字符串(用StrUtils工具类)
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
* @author yaohuaipeng
* @date 2018/10/26-16:16
*/
public class StrUtils {
/**
* 把逗号分隔的字符串转换字符串数组
*
* @param str
* @return
*/
public static String[] splitStr2StrArr(String str,String split) {
if (str != null && !str.equals("")) {
return str.split(split);
}
return null;
}
/**
* 把逗号分隔字符串转换List的Long
*
* @param str
* @return
*/
public static List<Long> splitStr2LongArr(String str) {
String[] strings = splitStr2StrArr(str,",");
if (strings == null) return null;
List<Long> result = new ArrayList<>();
for (String string : strings) {
result.add(Long.parseLong(string));
}
return result;
}
/**
* 把逗号分隔字符串转换List的Long
*
* @param str
* @return
*/
public static List<Long> splitStr2LongArr(String str,String split) {
String[] strings = splitStr2StrArr(str,split);
if (strings == null) return null;
List<Long> result = new ArrayList<>();
for (String string : strings) {
result.add(Long.parseLong(string));
}
return result;
}
public static String getRandomString(int length) {
String str = "0123456789";
Random random = new Random();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < length; i++) {
int number = random.nextInt(10);
sb.append(str.charAt(number));
}
return sb.toString();
}
// 26字母+10数字
public static String getComplexRandomString(int length) {
String str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
Random random = new Random();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < length; i++) {
int number = random.nextInt(62);
sb.append(str.charAt(number));
}
return sb.toString();
}
public static String convertPropertiesToHtml(String properties){
//1:容量:6:32GB_4:样式:12:塑料壳
StringBuilder sBuilder = new StringBuilder();
String[] propArr = properties.split("_");
for (String props : propArr) {
String[] valueArr = props.split(":");
sBuilder.append(valueArr[1]).append(":").append(valueArr[3]).append("<br>");
}
return sBuilder.toString();
}
}
2.springdata-redis
A 导入jar
<!--spirngboot springdata对redis支持-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
B 配置属性
spring:
redis:
database: 0
host: 127.0.0.1
port: 6379
password: 123456
jedis:
pool:
max-wait: 2000ms
min-idle: 2
max-idle: 8
启动redis服务:找到redis安装目录,cmd运行,redis-server.exe redis.windows.conf
C 编写测试类(基于springboot的测试类(继承BaseTest即可),用springdataredis提供的bean直接操作redis)
public class RedisTest extends BaseTest {
@Autowired
private RedisTemplate redisTemplate;
@Test
public void test(){
redisTemplate.opsForValue().set("ceshi", "搞一条数据");
System.out.println(redisTemplate.opsForValue().get("ceshi"));
}
}
3.Md5技术
不可逆加密技术,只能加密不能解密。 只能做比对,一般用来加密用户登录蜜马。
我们要做的是把传入的蜜马进行加密和然后数据库查询出来的密文进行比对判断蜜马是否正确。
盐值:同一种加密算法,由于不同的盐值,加密出来就不一样。
每个用户都有自己盐值,就算是相同的蜜马,两个用户加密出来的密文也不一样。
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class MD5Utils {
/**
* 加密
* @param context 明文
*/
public static String encrypByMd5(String context) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(context.getBytes());//update处理
byte [] encryContext = md.digest();//调用该方法完成计算
int i;
StringBuffer buf = new StringBuffer("");
for (int offset = 0; offset < encryContext.length; offset++) {//做相应的转化(十六进制)
i = encryContext[offset];
if (i < 0) i += 256;
if (i < 16) buf.append("0");
buf.append(Integer.toHexString(i));
}
return buf.toString();
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
}
public static void main(String[] args) {
//1.准备需要加密的蜜马
String pwd = "1";
//2 准备盐值
String salt = StrUtils.getComplexRandomString(32);
//2.加密 + 盐
String pwdMd5 = MD5Utils.encrypByMd5(pwd + salt);
// 将盐值和秘文存储到数据
System.out.println(pwdMd5);
//模拟登陆蜜马校验
String pwdStr = pwd;
String saltTem = salt;//相当于从数据库查询出来
String pwdTmpMd5 = MD5Utils.encrypByMd5(pwdStr + saltTem);
System.out.println("==="+pwdTmpMd5);
if(pwdTmpMd5.equals(pwdMd5)){
System.out.println("登陆成功");
}else{
System.out.println("蜜马错误");
}
}
}
4.短信接口
http://sms.webchinese.com.cn/Rates.shtml
用户信息修改,修改短信秘钥
导入依赖包
<dependency>
<groupId>commons-httpclient</groupId>
<artifactId>commons-httpclient</artifactId>
<version>3.1</version>
</dependency>
代码应用(工具类 常量类)
public static void sendsms(String phones, String message) throws IOException {
HttpClient client = new HttpClient();
PostMethod post = new PostMethod("https://utf8api.smschinese.cn/");
post.addRequestHeader("Content-Type","application/x-www-form-urlencoded;charset=utf-8");//在头文件中设置转码
NameValuePair[] data ={ new NameValuePair("Uid", "mingfeng"),new NameValuePair("Key", "d41d8cd98f00b204e980"),new NameValuePair("smsMob",phones),new NameValuePair("smsText",message)};
post.setRequestBody(data);
client.executeMethod(post);
Header[] headers = post.getResponseHeaders();
int statusCode = post.getStatusCode();
System.out.println("statusCode:"+statusCode); //HTTP状态码
for(Header h : headers){
System.out.println(h.toString());
}
String result = new String(post.getResponseBodyAsString().getBytes("utf-8"));
System.out.println(result); //打印返回消息状态
post.releaseConnection();
}
发送短信验证码
前端页面准备
1.拷贝register.html到根目录,更新引用路径
2.index.html设置在新标签页打开登录界面: target=“_blank”
<div class="menu-hd">
<a href="#" target="_top" class="h">亲,请登录</a>
<a href="register.html" target="_blank">免费注册</a>
</div>
<div class="member-logout">
<a class="am-btn-warning btn" href="login.html">登录</a>
<a class="am-btn-warning btn" target="_blank" href="register.html">注册</a>
</div>
3.静态项目引入js插件vue和axios
拷贝ph-admin\node_modules里的axios和vue到ph-web/js/plugin目录里并在register.html页面引入
<!--引入vue和axios-->
<script src="js/plugin/vue/dist/vue.min.js"></script>
<script src="js/plugin/axios/dist/axios.js"></script>
<!--全局使用-->
<script src="js/common.js"></script>
<!-- 局部使用
<script type="text/javascript">
//配置axios的全局基本路径
/*axios.defaults.baseURL='/api' 前端解决跨域*/
axios.defaults.baseURL='http://localhost:8080/'
//全局属性配置,在任意组件内可以使用this.$http获取axios对象
Vue.prototype.$http = axios
</script>-->
在js目录下新建common.js
//配置axios的全局基本路径
/*axios.defaults.baseURL='/api' 前端解决跨域*/
axios.defaults.baseURL='http://localhost:8080/'
//全局属性配置,在任意组件内可以使用this.$http获取axios对象
Vue.prototype.$http = axios
绑定div节点
<div class="am-tab-panel" id="myDiv">
写vue.js
<script type="text/javascript">
new Vue({
el:"#myDiv",
mounted(){
alert(this.$http)
}
})
</script>
获取的a标签换为按钮(方便置灰和显示秒数倒计时),绑定方法
<!--<a class="btn" href="javascript:void(0);" οnclick="sendMobileCode();" id="sendMobileCode">
<span id="dyMobileButton">获取</span></a>-->
<button type="button" @click="sendMobileCode">获取</button>
methods:{
sendMobileCode(){
alert(123)
}
},
点击获取拿到手机号
<input type="tel" v-model="phoneUserForm.phone" name="" id="phone" placeholder="请输入手机号">
<script type="text/javascript">
new Vue({
el:"#myDiv",
data:{
phoneUserForm:{
phone:18696148335
}
},
methods:{
sendMobileCode(){
alert(this.registerForm.phone)
}
},
mounted(){
// alert(this.$http)
}
})
</script>
<script type="text/javascript">
new Vue({
el:"#myDiv",
data:{
phoneUserForm:{
phone:""
}
},
methods:{
sendMobileCode(){
//1.判断手机号不为空
if(!this.phoneUserForm.phone){
alert("手机号不能为空");
return;
}
//2.获取按钮 禁用按钮 发送时灰化不能使用 发送成功倒计时60才能使用 如果发送失败立即可以发送
var sendBtn = $(event.target);
sendBtn.attr("disabled",true);
this.$http.post('/verifycode/smsCode',
{"phone":this.phoneUserForm.phone}).then((res) => {
console.log(res);
var ajaxResult = res.data;
if(ajaxResult.success){
alert("手机验证码已经发送到您的手机,请在3分钟内使用");
//3.1.发送成:倒计时
var time = 60;
var interval = window.setInterval( function () {
//每一条倒计时减一
time = time - 1 ;
//把倒计时时间搞到按钮上
sendBtn.html(time);
//3.2.倒计时完成恢复按钮
if(time <= 0){
sendBtn.html("重发");
sendBtn.attr("disabled",false);
//清除定时器
window.clearInterval(interval);
}
},1000);
}else{
//3.3.发送失败:提示,恢复按钮
sendBtn.attr("disabled",false);
alert("发送失败:"+ajaxResult.message);
}
});
}
},
mounted(){
// alert(this.$http)
}
})
</script>
发送短信后台
搭建用户模块
User,loginInfo
搭建完模块后记得yml配置domain和query的扫描路径(别名)
单独写公共可用的发短信接口
VerifycodeController
@RestController
@RequestMapping("/verifycode")
public class VerifycodeController {
@Autowired
private IVerifycodeService verifycodeService;
/**
* 发送短信验证码
* @param phone 给谁发送 只是允许传入一个手机号
* @return
*/
@PostMapping("/smsCode")
public AjaxResult sendSmsCode(@RequestBody String phone){
try {
verifycodeService.sendSmsCode(phone);
return AjaxResult.me();
} catch (BusinessException e) {
return AjaxResult.me().setMessage("发送失败!"+e.getMessage());
}catch (Exception e) {
e.printStackTrace();
return AjaxResult.me().setMessage("系统繁忙,稍后重试!!");
}
}
}
VerifycodeServiceImpl
//1.校验
//1.1手机号空校验
//1.2不能被注册
//2.判断是否存在验证码(从redis中获取电话对应的验证码)
//2.1 存在
//2.1.1 判断是否过了重发时间(1分钟)
//2.1.1.1 如果没过重发时间。报错:请勿重复发送
//2.1.1.2 如果过了,使用原来还没过期的验证码,刷新过期时间
//2.2 不存在
//2.2.1直接生成验证码
//3 保存验证码到redis 设置过期时间
//4 发送短信(使用工具类掉短信接口即可)
@Service
@Transactional(readOnly = true,propagation = Propagation.SUPPORTS)
public class VerifycodeServiceImpl implements IVerifycodeService {
@Autowired
private IUserService userService;
@Autowired
private RedisTemplate redisTemplate;
@Override
public void sendSmsCode(String phone) {
//1.校验
//1.1手机号空校验
if(!StringUtils.hasLength(phone)){
throw new BusinessException("手机号不能为空!");
}
//1.2不能被注册
User user = userService.loadUserByPhone(phone);
if(user != null){
throw new BusinessException("用户已经你存在!");
}
//2.判断是否存在验证码(从redis中获取电话对应的验证码)
Object codeObj = redisTemplate.opsForValue().get(UserConstant.USER_VERFIY_CODE+":"+phone);
String code = "";
if(codeObj !=null){
//2.1 存在
String value =(String)codeObj;
String timeStr = value.split(":")[1];//时间戳
//2.1.1 判断是否过了重发时间(1分钟)
if(System.currentTimeMillis()-Long.valueOf(timeStr) <= 1*60*1000){
//2.1.1.1 如果没过重发时间。报错:请勿重复发送
throw new BusinessException("请勿重复发送验证码!!!");
}
//2.1.1.2 如果过了,使用原来还没过期的验证码,刷新过期时间
code = value.split(":")[0];//验证码
}else{//2.2 不存在
//2.2.1直接生成验证码
code = StrUtils.getComplexRandomString(4);
}
//拼接存储验证码的格式
String codeValue = code + ":" + System.currentTimeMillis();
//3 保存验证码到redis 设置过期时间
redisTemplate.opsForValue().set(UserConstant.USER_VERFIY_CODE+":"+phone, codeValue, 3, TimeUnit.MINUTES);
//4 发送短信(使用工具类掉短信接口即可)
/*SmsUtils.sendSms(phone, "您的验证码是:"+code+"; 请在3分钟内使用!");*/
System.out.println("您的验证码是:"+code+"; 请在3分钟内使用!");
}
}