一. 关于js的启动服务

传统导入js开发的模式没法直接服务器方式调试,需要安装live-server服务器,这个服务器就是node一个模块

1
2
3
4
安装:npm install -g live-server
# 指定端口80
启动项目:live-server --port=80
注意:live-server服务器自带热刷新

二. 使用图形验证码

1. 前端代码

1
2
3
4
5
<div class="user-phone">
<label for="imageCode"><i class="am-icon-check am-icon-sm"></i></label>
<input type="text" name="" style="width: 180px;" v-model="phoneUserForm.imageCode" id="imageCode" placeholder="请输入图片验证码">
<img id="captcha-image-temp" @click="getImageCode":src="base64ImageCode" class="captcha-image-temp" alt="点击换图" title="点击换图" style="vertical-align: middle; cursor: pointer;">
</div>

2. 流程分析

1
2
3
4
5
6
7
8
9
10
11
12
1. 前端页面一加载或点击重新获取使用axios发送异步请求获取验证码
2. 后端生成图形验证码,并将验证码保存在redis
3. 后端使用Base64将图片编码成字符串,响应给前端
4. 前端使用vue指令base64编码之后的字符串放在对应位置,最终显示图片
<img src="这里"/>
5. 在点击获取手机验证码的时候验证图形验证码是否正确
防止非法人员通过脚本恶意点击获取手机验证码

后端redis怎么保存图形验证码?
1. 设置过期时间 - 简单
2. key:UUID - 需要在前后端之间进行传输。浏览器的localStorage保存
value:验证码
1
2
3
4
5
6
7
8
9
1. 请求图片验证码之前判断localStorage是否有KEY,如果没就创建,然后保存到localStorage,如果有自己直接作为参数发送Ajax请求获取图片验证码
2. 前端通过ajax发送一个图片验证码请求,携带者KEY
3. 后端收到请求,生成验证码的值
4. 把验证码的存储到Redis,以前段传入的key作为Redis的key
5. 把验证码合并到一个图片中
6. 把图片基于Base64编码层字符串,响应给前端
7. 前端拿到base64字符串,进行图片的展示,用户输入图片验证码

校验过程:前台提交注册请求 ,验证图片验证码(key也要携带),后端把前端传入的图片验证码的值和Redis中的图片验证码的值做比较

3. 前端存储数据

sessionStorage:存放的数据只在当前窗口有效。基本不用。不可以跨窗口;
localStorage:持久化存储。只要不删除,在当前浏览器永远有效。可以跨窗口,不能跨浏览器。但是不同的域名共享数据的,即在后台系统保存在localStorage的数据在前台系统是获取不到的。

4. 前端功能代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<script>
new Vue({
el: '#app',
data: {
base64ImageCode: '', //获取图形验证码响应一个base64位的字符串
base64ImageCodePrefix: 'data:image/jpeg;base64,',
phoneUserForm: {
imageCode: ''
},
},
methods: {
//生成随机图形验证码的key
createUUID() {
let s = [];
let hexDigits = "0123456789abcdefghi";
for (let i = 0; i < 36; i++) {
s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
}
s[14] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
s[8] = s[13] = s[18] = s[23] = "-";
let uuid = s.join("");
return uuid;
},
// 获取图形验证码
getImageCode() {
//获取localhost的键值 verifyCodeKey
let key = localStorage.getItem('verifyCodeKey')
if (!key) {// 如果key不存在就重新生成存储到本地
key = this.createUUID();
localStorage.setItem('verifyCodeKey', key)
}
//发送异步请求获取base64编码
this.$http.get('/verifyCode/image/' + key).then((res) => {
// 在回调函数获取响应的数据与拼接前缀 绑定到src
this.base64ImageCode = this.base64ImageCodePrefix + res.data.msg
}).catch(() => {
alert('服务器繁忙,请稍后再试!!!')
})
}
},
mounted() {
this.getImageCode();
}
})
</script>

5. 后端业务实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Service
public class VerifyCodeServiceImpl implements VerifyCodeService {
@Autowired
private StringRedisTemplate stringRedisTemplate;

@Override
public Result getImgCode(String key) {
// 获取随机验证码长度为4
final String code = VerifyCodeUtils.generateVerifyCode(4);
// 将验证码的值存入redis中 有效期五分钟
stringRedisTemplate.opsForValue().set("cache:code:image:" + key, code, 5L, TimeUnit.MINUTES);
// 把验证码的值合并到图片,设置宽度和高度 使用Base64编码。并返回base64编码的字符串
return Result.success(VerifyCodeUtils.verifyCode(115, 40, code));
}
}

6. 生成图形验证码工具类

VerifyCodeUtils

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
package io.coderyeah.basic.util;

import io.coderyeah.basic.exception.BusinessException;
import sun.misc.BASE64Encoder;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Random;

public class VerifyCodeUtils {
//使用到Algerian字体,系统里没有的话需要安装字体,字体只显示大写,去掉了1,0,i,o几个容易混淆的字符
public static final String VERIFY_CODES = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";
private static Random random = new Random();


/**
* 使用系统默认字符源生成验证码
*
* @param verifySize 验证码长度
* @return
*/
public static String generateVerifyCode(int verifySize) {
return generateVerifyCode(verifySize, VERIFY_CODES);
}

/**
* 使用指定源生成验证码
*
* @param verifySize 验证码长度
* @param sources 验证码字符源
* @return
*/
public static String generateVerifyCode(int verifySize, String sources) {
if (sources == null || sources.length() == 0) {
sources = VERIFY_CODES;
}
int codesLen = sources.length();
Random rand = new Random(System.currentTimeMillis());
StringBuilder verifyCode = new StringBuilder(verifySize);
for (int i = 0; i < verifySize; i++) {
verifyCode.append(sources.charAt(rand.nextInt(codesLen - 1)));
}
return verifyCode.toString();
}

/**
* 输出指定验证码图片流
*
*/
public static void outputImage(int w, int h, OutputStream os, String code) throws IOException {
int verifySize = code.length();
BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
Random rand = new Random();
Graphics2D g2 = image.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
Color[] colors = new Color[5];
Color[] colorSpaces = new Color[]{Color.WHITE, Color.CYAN,
Color.GRAY, Color.LIGHT_GRAY, Color.MAGENTA, Color.ORANGE,
Color.PINK, Color.YELLOW};
float[] fractions = new float[colors.length];
for (int i = 0; i < colors.length; i++) {
colors[i] = colorSpaces[rand.nextInt(colorSpaces.length)];
fractions[i] = rand.nextFloat();
}
Arrays.sort(fractions);

g2.setColor(Color.GRAY);// 设置边框色
g2.fillRect(0, 0, w, h);

Color c = getRandColor(200, 250);
g2.setColor(c);// 设置背景色
g2.fillRect(0, 2, w, h - 4);

//绘制干扰线
Random random = new Random();
g2.setColor(getRandColor(160, 200));// 设置线条的颜色
for (int i = 0; i < 20; i++) {
int x = random.nextInt(w - 1);
int y = random.nextInt(h - 1);
int xl = random.nextInt(6) + 1;
int yl = random.nextInt(12) + 1;
g2.drawLine(x, y, x + xl + 40, y + yl + 20);
}

// 添加噪点
float yawpRate = 0.05f;// 噪声率
int area = (int) (yawpRate * w * h);
for (int i = 0; i < area; i++) {
int x = random.nextInt(w);
int y = random.nextInt(h);
int rgb = getRandomIntColor();
image.setRGB(x, y, rgb);
}

shear(g2, w, h, c);// 使图片扭曲

g2.setColor(getRandColor(100, 160));
int fontSize = h - 4;
Font font = new Font("Algerian", Font.ITALIC, fontSize);
g2.setFont(font);
char[] chars = code.toCharArray();
for (int i = 0; i < verifySize; i++) {
AffineTransform affine = new AffineTransform();
affine.setToRotation(Math.PI / 4 * rand.nextDouble() * (rand.nextBoolean() ? 1 : -1), (w / verifySize) * i + fontSize / 2, h / 2);
g2.setTransform(affine);
g2.drawChars(chars, i, 1, ((w - 10) / verifySize) * i + 5, h / 2 + fontSize / 2 - 10);
}

g2.dispose();
ImageIO.write(image, "jpg", os);
}

private static Color getRandColor(int fc, int bc) {
if (fc > 255)
fc = 255;
if (bc > 255)
bc = 255;
int r = fc + random.nextInt(bc - fc);
int g = fc + random.nextInt(bc - fc);
int b = fc + random.nextInt(bc - fc);
return new Color(r, g, b);
}

private static int getRandomIntColor() {
int[] rgb = getRandomRgb();
int color = 0;
for (int c : rgb) {
color = color << 8;
color = color | c;
}
return color;
}

private static int[] getRandomRgb() {
int[] rgb = new int[3];
for (int i = 0; i < 3; i++) {
rgb[i] = random.nextInt(255);
}
return rgb;
}

private static void shear(Graphics g, int w1, int h1, Color color) {
shearX(g, w1, h1, color);
shearY(g, w1, h1, color);
}

private static void shearX(Graphics g, int w1, int h1, Color color) {

int period = random.nextInt(2);

boolean borderGap = true;
int frames = 1;
int phase = random.nextInt(2);

for (int i = 0; i < h1; i++) {
double d = (double) (period >> 1)
* Math.sin((double) i / (double) period
+ (6.2831853071795862D * (double) phase)
/ (double) frames);
g.copyArea(0, i, w1, 1, (int) d, 0);
if (borderGap) {
g.setColor(color);
g.drawLine((int) d, i, 0, i);
g.drawLine((int) d + w1, i, w1, i);
}
}

}

private static void shearY(Graphics g, int w1, int h1, Color color) {

int period = random.nextInt(40) + 10; // 50;

boolean borderGap = true;
int frames = 20;
int phase = 7;
for (int i = 0; i < w1; i++) {
double d = (double) (period >> 1)
* Math.sin((double) i / (double) period
+ (6.2831853071795862D * (double) phase)
/ (double) frames);
g.copyArea(i, 0, 1, h1, 0, (int) d);
if (borderGap) {
g.setColor(color);
g.drawLine(i, (int) d, i, 0);
g.drawLine(i, (int) d + h1, i, h1);
}

}

}

/**
* 获取随机验证码及其加密图片
*
*/
public static String verifyCode(int w, int h, String code){
try {
//base64编码器
BASE64Encoder encoder = new BASE64Encoder();
//准备输出流
ByteArrayOutputStream data = new ByteArrayOutputStream();
//使用code生成w宽 h高的图片,并将结果图片存入data流中
outputImage(w, h, data, code);
//使用base64编码成String
return encoder.encode(data.toByteArray());
} catch (IOException e) {
e.printStackTrace();
throw new BusinessException("生成验证码失败!");
}
}

public static void main(String[] args) throws Exception{
System.out.println(verifyCode(100, 30, "1234"));
}
}

StrUtils

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
package io.coderyeah.basic.util;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;


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();

}

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();
}

}

三. 数据库三范式

1
2
3
4
5
6
7
8
9
10
11
12
13
3FN:
1. 第一范式:列唯一
列不可再分,保持原子性,关系型数据库默认支持
2. 第二范式:行唯一
每一行的数据要唯一区分。怎么区分呢?需要加一列作为惟一标识。这列被称为主键
并且行中每一列数据都与主键相关
3. 第三范式:如果一张表的数据能够通过其他表推导出来,不应该单独设计,通过外键的方式关联查询出来
减少数据冗余

总结:三大范式只是一般设计数据库的基本理念,可以建立冗余较小、结构合理的数据库。如果有特殊情况,当然要特殊对待,数据库设计最重要的是看需求跟性能,需求>性能>表结构。所以不能一味的去追求范式建立数据库表

3FN
原则上是不能违反三范式的,但是有的时候我们为了增强查询效率【不用关联查询,直接单表查询】,会设计一些冗余字段,变多表查询为单表查询