抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

Hello world!

认证服务 - 短信验证码

基础环境搭建

配置

开启nacos,feign

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# spring
spring:
# nacos 注册中心
cloud:
nacos:
discovery:
# nacos地址
server-addr: 127.0.0.1:8848
# 服务名
application:
name: gulimall-auto-server
jackson:
# 时间戳格式化
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
thymeleaf:
# thymeleaf缓存
cache: false
#服务器
server:
port: 20000

资源

放入登录注册页面文件,静态资源放到nacos中

配置

  1. host
  2. gateway

viewController

templates下不为index名的html页面都需要通过controller映射后跳转

如果只是跳转不写任何其他方法,可以使用viewController

1
2
3
4
5
6
7
8
9
10
11
@Configuration
public class MyWebConfig implements WebMvcConfigurer {
/*
* 重写,可以添加urlPath和视图名的对应映射
*/
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login.html").setViewName("login");
registry.addViewController("/reg.html").setViewName("reg");
}
}

发送验证码倒计时

使用定时器

1
2
3
4
5
6
7
8
9
10
<!-- 验证码 -->
<div class="register-box">
<label class="other_label">验 证 码
<input name="code" maxlength="20" type="text" placeholder="请输入验证码" class="caa">
</label>
<a id="sendCode"> 发送验证码 </a>
<div class="tips" style="color:red"
th:text="${errors!=null?(#maps.containsKey(errors, 'code')?errors.code:''):''}">
</div>
</div>
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
$(function () {
$("#sendCode").click(function () {
//2、倒计时
if ($(this).hasClass("disabled")) {
//正在倒计时。
} else {
//1、给指定手机号发送验证码
$.get("/sms/sendcode?phone=" + $("#phoneNum").val(), function (data) {
if (data.code != 0) {
alert(data.msg);
}
});
timeoutChangeStyle();
}
});
})
var num = 60;

function timeoutChangeStyle() {
$("#sendCode").attr("class", "disabled");
if (num == 0) {
$("#sendCode").text("发送验证码");
num = 60;
$("#sendCode").attr("class", "");
} else {
var str = num + "s 后再次发送";
$("#sendCode").text(str);
setTimeout("timeoutChangeStyle()", 1000);
}
num--;
}

短信验证码

写在第三方工具的模块中

HttpClient

1
2
3
4
5
6
7
8
9
/**
* 重要提示如下:
* HttpUtils请从
* https://github.com/aliyun/api-gateway-demo-sign-java/blob/master/src/main/java/com/aliyun/api/gateway/demo/util/HttpUtils.java
* 下载
*
* 相应的依赖请参照
* https://github.com/aliyun/api-gateway-demo-sign-java/blob/master/pom.xml
*/
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
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
package com.atguigu.gulimall.thirdparty.utils;

import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class HttpUtils {

/**
* get
*
* @param host
* @param path
* @param method
* @param headers
* @param querys
* @return
* @throws Exception
*/
public static HttpResponse doGet(String host, String path, String method,
Map<String, String> headers,
Map<String, String> querys)
throws Exception {
HttpClient httpClient = wrapClient(host);

HttpGet request = new HttpGet(buildUrl(host, path, querys));
for (Map.Entry<String, String> e : headers.entrySet()) {
request.addHeader(e.getKey(), e.getValue());
}

return httpClient.execute(request);
}

/**
* post form
*
* @param host
* @param path
* @param method
* @param headers
* @param querys
* @param bodys
* @return
* @throws Exception
*/
public static HttpResponse doPost(String host, String path, String method,
Map<String, String> headers,
Map<String, String> querys,
Map<String, String> bodys)
throws Exception {
HttpClient httpClient = wrapClient(host);

HttpPost request = new HttpPost(buildUrl(host, path, querys));
for (Map.Entry<String, String> e : headers.entrySet()) {
request.addHeader(e.getKey(), e.getValue());
}

if (bodys != null) {
List<NameValuePair> nameValuePairList = new ArrayList<NameValuePair>();

for (String key : bodys.keySet()) {
nameValuePairList.add(new BasicNameValuePair(key, bodys.get(key)));
}
UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(nameValuePairList, "utf-8");
formEntity.setContentType("application/x-www-form-urlencoded; charset=UTF-8");
request.setEntity(formEntity);
}

return httpClient.execute(request);
}

/**
* Post String
*
* @param host
* @param path
* @param method
* @param headers
* @param querys
* @param body
* @return
* @throws Exception
*/
public static HttpResponse doPost(String host, String path, String method,
Map<String, String> headers,
Map<String, String> querys,
String body)
throws Exception {
HttpClient httpClient = wrapClient(host);

HttpPost request = new HttpPost(buildUrl(host, path, querys));
for (Map.Entry<String, String> e : headers.entrySet()) {
request.addHeader(e.getKey(), e.getValue());
}

if (StringUtils.isNotBlank(body)) {
request.setEntity(new StringEntity(body, "utf-8"));
}

return httpClient.execute(request);
}

/**
* Post stream
*
* @param host
* @param path
* @param method
* @param headers
* @param querys
* @param body
* @return
* @throws Exception
*/
public static HttpResponse doPost(String host, String path, String method,
Map<String, String> headers,
Map<String, String> querys,
byte[] body)
throws Exception {
HttpClient httpClient = wrapClient(host);

HttpPost request = new HttpPost(buildUrl(host, path, querys));
for (Map.Entry<String, String> e : headers.entrySet()) {
request.addHeader(e.getKey(), e.getValue());
}

if (body != null) {
request.setEntity(new ByteArrayEntity(body));
}

return httpClient.execute(request);
}

/**
* Put String
*
* @param host
* @param path
* @param method
* @param headers
* @param querys
* @param body
* @return
* @throws Exception
*/
public static HttpResponse doPut(String host, String path, String method,
Map<String, String> headers,
Map<String, String> querys,
String body)
throws Exception {
HttpClient httpClient = wrapClient(host);

HttpPut request = new HttpPut(buildUrl(host, path, querys));
for (Map.Entry<String, String> e : headers.entrySet()) {
request.addHeader(e.getKey(), e.getValue());
}

if (StringUtils.isNotBlank(body)) {
request.setEntity(new StringEntity(body, "utf-8"));
}

return httpClient.execute(request);
}

/**
* Put stream
*
* @param host
* @param path
* @param method
* @param headers
* @param querys
* @param body
* @return
* @throws Exception
*/
public static HttpResponse doPut(String host, String path, String method,
Map<String, String> headers,
Map<String, String> querys,
byte[] body)
throws Exception {
HttpClient httpClient = wrapClient(host);

HttpPut request = new HttpPut(buildUrl(host, path, querys));
for (Map.Entry<String, String> e : headers.entrySet()) {
request.addHeader(e.getKey(), e.getValue());
}

if (body != null) {
request.setEntity(new ByteArrayEntity(body));
}

return httpClient.execute(request);
}

/**
* Delete
*
* @param host
* @param path
* @param method
* @param headers
* @param querys
* @return
* @throws Exception
*/
public static HttpResponse doDelete(String host, String path, String method,
Map<String, String> headers,
Map<String, String> querys)
throws Exception {
HttpClient httpClient = wrapClient(host);

HttpDelete request = new HttpDelete(buildUrl(host, path, querys));
for (Map.Entry<String, String> e : headers.entrySet()) {
request.addHeader(e.getKey(), e.getValue());
}

return httpClient.execute(request);
}

private static String buildUrl(String host, String path, Map<String, String> querys) throws UnsupportedEncodingException {
StringBuilder sbUrl = new StringBuilder();
sbUrl.append(host);
if (!StringUtils.isBlank(path)) {
sbUrl.append(path);
}
if (null != querys) {
StringBuilder sbQuery = new StringBuilder();
for (Map.Entry<String, String> query : querys.entrySet()) {
if (0 < sbQuery.length()) {
sbQuery.append("&");
}
if (StringUtils.isBlank(query.getKey()) && !StringUtils.isBlank(query.getValue())) {
sbQuery.append(query.getValue());
}
if (!StringUtils.isBlank(query.getKey())) {
sbQuery.append(query.getKey());
if (!StringUtils.isBlank(query.getValue())) {
sbQuery.append("=");
sbQuery.append(URLEncoder.encode(query.getValue(), "utf-8"));
}
}
}
if (0 < sbQuery.length()) {
sbUrl.append("?").append(sbQuery);
}
}

return sbUrl.toString();
}

private static HttpClient wrapClient(String host) {
HttpClient httpClient = new DefaultHttpClient();
if (host.startsWith("https://")) {
sslClient(httpClient);
}

return httpClient;
}

private static void sslClient(HttpClient httpClient) {
try {
SSLContext ctx = SSLContext.getInstance("TLS");
X509TrustManager tm = new X509TrustManager() {
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}

@Override
public void checkClientTrusted(X509Certificate[] xcs, String str) {

}

@Override
public void checkServerTrusted(X509Certificate[] xcs, String str) {

}
};
ctx.init(null, new TrustManager[]{tm}, null);
SSLSocketFactory ssf = new SSLSocketFactory(ctx);
ssf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
ClientConnectionManager ccm = httpClient.getConnectionManager();
SchemeRegistry registry = ccm.getSchemeRegistry();
registry.register(new Scheme("https", 443, ssf));
} catch (KeyManagementException ex) {
throw new RuntimeException(ex);
} catch (NoSuchAlgorithmException ex) {
throw new RuntimeException(ex);
}
}
}

HttpClient是阿里云已经封装好的

阿里云搜短信,然后找到下面的第三方提供的服务,不过模板要联系客服?我不理解

这块不记录了,短信还是用不了

SmsComponent

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
@ConfigurationProperties(prefix = "gulimall.alicloud.sms")    // 外部化配置
@Component
@Data
public class SmsComponent {

private String host;
private String path;
private String appcode;

// 发送短信验证码
public void sendCode(String phone, String code) {
String method = "POST";
Map<String, String> headers = new HashMap<String, String>();
//最后在header中的格式(中间是英文空格)为Authorization:APPCODE 83359fd73fe94948385f570e3c139105
headers.put("Authorization", "APPCODE " + appcode);
Map<String, String> querys = new HashMap<String, String>();
querys.put("content", "【gulimall】你的验证码是:" + code + ",3分钟内有效!");
querys.put("mobile", phone);
Map<String, String> bodys = new HashMap<String, String>();
try {
/**
* 重要提示如下:
* HttpUtils请从
* https://github.com/aliyun/api-gateway-demo-sign-java/blob/master/src/main/java/com/aliyun/api/gateway/demo/util/HttpUtils.java
* 下载
*
* 相应的依赖请参照
* https://github.com/aliyun/api-gateway-demo-sign-java/blob/master/pom.xml
*/
HttpResponse response = HttpUtils.doPost(host, path, method, headers, querys, bodys);
System.out.println(response.toString());
//获取response的body
//System.out.println(EntityUtils.toString(response.getEntity()));
} catch (Exception e) {
e.printStackTrace();
}
}
}
1
2
3
4
5
6
7
# 自定义 短信服务配置
gulimall:
alicloud:
sms:
host: https://cxwg.market.alicloudapi.com
path: /sendSms
appcode: 你自己的AppCode

SmsSendController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@RestController
@RequestMapping("/sms")
public class SmsSendController {

@Autowired
SmsComponent smsComponent;

/*
* 提供调用接口
*/
@GetMapping("/sms/sendCode")
public R sendCode(@RequestParam("phone") String phone, @RequestParam("code") String code) {
smsComponent.sendCode(phone, code);
return R.ok();
}

}

远程调用发送短信验证码服务

auth模块调用third-part模块的短信服务

ThirdPartFeignService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.atguigu.gulimall.auth.feign;

import com.atguigu.common.utils.R;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

/*
* 第三方服务的远程接口
*/
@FeignClient
public interface ThirdPartFeignService {
// 远程调用发送短信验证码服务
@GetMapping("/sms/sendCode")
R sendCode(@RequestParam("phone") String phone, @RequestParam("code") String code);
}

LoginController

1
2
3
4
5
6
7
8
9
10
11
12
13
@RestController
public class LoginController {
@Autowired
ThirdPartFeignService thirdPartFeignService;
@ResponseBody
@GetMapping("/sms/sendcode")
public R sendCode(@RequestParam("phone") String phone) {
// 随机生成验证码
String code = UUID.randomUUID().toString().substring(0, 5);
thirdPartFeignService.sendCode(phone, code);
return R.ok();
}
}

验证码防刷验证

防刷+校验时间

使用redis并配置好地址

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
@RestController
public class LoginController {

@Autowired
ThirdPartFeignService thirdPartFeignService;

@Autowired
StringRedisTemplate redisTemplate;

@ResponseBody
@GetMapping("/sms/sendcode")
public R sendCode(@RequestParam("phone") String phone) {
// 判断redis中是否有该手机号的验证码,并且在过期时间内
String existCode = redisTemplate.opsForValue().get(AuthServerConstant.SMS_CODE_CACHE_PREDIX + phone);
if (!StringUtils.isEmpty(existCode)) {
long preTime = Long.parseLong(existCode.split("_")[1]);
if ((System.currentTimeMillis() - preTime) < 60000) {
// 存在小于六十秒,不能再发验证码
return R.error(BizCodeEnum.SMS_CODE_EXCEPTION.getCode(), BizCodeEnum.SMS_CODE_EXCEPTION.getMsg());
}
}
// 随机生成验证码+系统时间,防刷
String code = UUID.randomUUID().toString().substring(0, 5);
// 存入redis(key:value == prefix+phone:code),并设置超时时间
redisTemplate.opsForValue().set(AuthServerConstant.SMS_CODE_CACHE_PREDIX + phone, code + "_" + System.currentTimeMillis(), 10, TimeUnit.MINUTES);
// 发送短信
thirdPartFeignService.sendCode(phone, code);
return R.ok();
}

}

认证服务 - 注册

使用JSR303校验

Auth模块

UserRegistVo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Data
public class UserRegistVo {
// JSR303校验
@NotEmpty(message = "用户名必须提交")
@Length(min = 6, max = 18, message = "用户名必须是6-18位字符")
private String userName;

@NotEmpty(message = "密码必须填写")
@Length(min = 6, max = 18, message = "密码必须是6-18位字符")
private String password;

@NotEmpty(message = "手机号必须填写")
@Pattern(regexp = "^[1]([3-9])[0-9]{9}$", message = "手机号格式不正确")
private String phone;

@NotEmpty(message = "验证码必须填写")
private String code;
}

MemberFeignService

1
2
3
4
5
6
7
8
@FeignClient("gulimall-member")
public interface MemberFeignService {
/**
* 注册会员
*/
@PostMapping("/member/member/regist")
R regist(@RequestBody UserRegistVo registVo);
}

LoginController

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
@PostMapping("/regist")
public String register(@Valid UserRegistVo vo, BindingResult bindingResult,
RedirectAttributes attributes) {
if (bindingResult.hasErrors()) {
Map<String, String> errors = bindingResult.getFieldErrors().stream().collect(Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage));
// 校验有错误
// RedirectAttributes 用于重定向带数据
attributes.addFlashAttribute("errors", errors);
return "redirect:http://auth.gulimall.com/reg.html";
}
// 远程注册服务
// 1 校验验证码
String inputCode = vo.getCode();
String existCode = redisTemplate.opsForValue().get(AuthServerConstant.SMS_CODE_CACHE_PREDIX + vo.getPhone());
if (!StringUtils.isEmpty(existCode)) {
if (inputCode.equals(existCode.split("_")[0])) {
// 验证码正确
// 删除验证码
redisTemplate.delete(AuthServerConstant.SMS_CODE_CACHE_PREDIX + vo.getPhone());
// 远程调用会员服务
R r = memberFeignService.regist(vo);
if (r.getCode() == 0) {
// 注册成功
return "redirect:http://auth.gulimall.com/login.html";
} else {
// 远程服务异常
Map<String, String> errors = new HashMap<String, String>();
errors.put("msg", r.getMsg());
attributes.addFlashAttribute("errors", errors);
return "redirect:http://auth.gulimall.com/reg.html";
}

} else {
// 验证码错误
Map<String, String> errors = new HashMap<String, String>();
errors.put("code", "验证码错误");
attributes.addFlashAttribute("errors", errors);
return "redirect:http://auth.gulimall.com/reg.html";
}
} else {
// 不存在验证码
Map<String, String> errors = new HashMap<String, String>();
errors.put("code", "验证码错误");
attributes.addFlashAttribute("errors", errors);
return "redirect:http://auth.gulimall.com/reg.html";
}
}

Member模块

MemberController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@RestController
@RequestMapping("member/member")
public class MemberController {
@Autowired
private MemberService memberService;

/**
* 注册会员
*/
@PostMapping("/regist")
public R regist(@RequestBody UserRegistVo registVo) {
try {
// 可能会抛已存在的异常
memberService.regist(registVo);
} catch (PhoneExistException e) {
return R.error(BizCodeEnum.PHONE_EXIST_EXCEPTION.getCode(), BizCodeEnum.PHONE_EXIST_EXCEPTION.getMsg());
} catch (UsernameExistException e) {
return R.error(BizCodeEnum.USERNAME_EXIST_EXCEPTION.getCode(), BizCodeEnum.USERNAME_EXIST_EXCEPTION.getMsg());
}
return R.ok();
}

MemberServiceImpl

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
@Service("memberService")
public class MemberServiceImpl extends ServiceImpl<MemberDao, MemberEntity> implements MemberService {
@Autowired
MemberLevelService levelService;
@Override
public void regist(UserRegistVo registVo) {
// 检查用户名和手机号的唯一性

MemberEntity member = new MemberEntity();
member.setUsername(registVo.getUserName());
member.setMobile(registVo.getPhone());

member.setLevelId(levelService.getDefaultLevel().getId());

// 判断是否已存在
checkPhoneUnique(registVo.getPhone());
checkUsernameUnique(registVo.getUserName());

// 加密
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
member.setPassword(passwordEncoder.encode(registVo.getPassword()));

baseMapper.insert(member);
}

@Override
public void checkPhoneUnique(String phone) throws PhoneExistException {
Integer mobile = baseMapper.selectCount(new QueryWrapper<MemberEntity>().eq("mobile", phone));
if (mobile > 0) {
throw new PhoneExistException();
}
}

@Override
public void checkUsernameUnique(String username) throws UsernameExistException {
Integer existUsername = baseMapper.selectCount(new QueryWrapper<MemberEntity>().eq("username", username));
if (existUsername > 0) {
throw new UsernameExistException();
}
}

}

异常机制

异常类UsernameExistException

1
2
3
4
5
public class UsernameExistException extends RuntimeException {
public UsernameExistException() {
super("用户名已存在");
}
}

异常类PhoneExistException

1
2
3
4
5
public class PhoneExistException extends RuntimeException {
public PhoneExistException() {
super("手机号已存在");
}
}

MD5盐值加密

image-20211117163550640

MD5可以使用彩虹表暴力破解,所以需要加盐

认证服务 - 账号登录

Auth模块

UserLoginVo

1
2
3
4
5
@Data
public class UserLoginVo {
private String loginacct;
private String password;
}

MemberFeignService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@FeignClient("gulimall-member")
public interface MemberFeignService {
/**
* 注册会员
*/
@PostMapping("/member/member/regist")
R regist(@RequestBody UserRegistVo registVo);

/**
* 会员登录
*/
@PostMapping("/member/member/login")
R login(@RequestBody UserLoginVo loginVo);
}

LoginController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@PostMapping("/login")
public String login(UserLoginVo vo, RedirectAttributes attributes) {
R r = memberFeignService.login(vo);
if (r.getCode() == 0) {
// 登录成功
// r取出来的值为map类型,map -> json -> bean
String memberJSON = JSONObject.toJSONString(r.get("member"));
MemberEntityVo memberEntityVo = JSON.parseObject(memberJSON, MemberEntityVo.class);
log.info("登陆成功,用户:{}", memberJSON);
// 保存用户信息至session,注意这里对象的类必须实现序列化,才能保存到redis中
session.setAttribute(AuthServerConstant.LOGIN_USER, memberEntityVo);
return "redirect:http://gulimall.com";
} else {
// 账号或密码错误
Map<String, String> errors = new HashMap<String, String>();
errors.put("msg", r.getMsg());
attributes.addFlashAttribute("errors", errors);
return "redirect:http://auth.gulimall.com/login.html";
}
}

Member模块

MemberController

1
2
3
4
5
6
7
8
9
10
11
/**
* 会员登录
*/
@PostMapping("/login")
public R login(@RequestBody UserLoginVo loginVo) {
MemberEntity member = memberService.login(loginVo);
if (member == null) {
return R.error(BizCodeEnum.LOGIN_FAILED_EXCEPTION.getCode(), BizCodeEnum.LOGIN_FAILED_EXCEPTION.getMsg());
}
return R.ok().put("member", member);
}

MemberServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
public MemberEntity login(UserLoginVo loginVo) {
String loginacct = loginVo.getLoginacct();
String password = loginVo.getPassword();
MemberEntity member = baseMapper.selectOne(new QueryWrapper<MemberEntity>().eq("username", loginacct).or().eq("mobile", loginacct));
if (member != null) {
// 判断密码是否匹配
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
boolean matches = passwordEncoder.matches(password, member.getPassword());
return matches ? member : null;
} else {
// 该账号不存在
return null;
}
}

认证服务 - 社交登录

OAuth2

OAuth: OAuth(开放授权) 是一个开放标准, 允许用户授权第三方网站访问他们存储
在另外的服务提供者上的信息, 而不需要将用户名和密码提供给第三方网站或分享他们
数据的所有内容。

OAuth2.0: 对于用户相关的 OpenAPI(例如获取用户信息, 动态同步, 照片, 日志, 分
享等) , 为了保护用户数据的安全和隐私, 第三方网站访问用户数据前都需要显式的向
用户征求授权

image-20211118133832701

image-20211118133746108

gitee授权登录

接口测试

gitee进入修改资料 -> 第三方应用 -> 创建应用,然后根据文档操作即可。

Gitee OAuth 文档

GiteeUserVo

1
2
3
4
5
6
7
8
9
10
@Data
public class GiteeUserVo {
private String access_token;
private String token_type;
private long expires_in;
private String refresh_token;
private String scope;
private long created_at;
private String uid;
}

MemberServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Override
public MemberEntity giteeLogin(GiteeUserVo loginVo, JSONObject userInfoJson) {
// 判断当前社交用户是否曾注册过(根据uid判断)
MemberEntity selectMember = baseMapper.selectOne(new QueryWrapper<MemberEntity>().eq("gitee_uid", loginVo.getGitee_uid()));
if (selectMember != null) {
// 该用户已注册,更换令牌和过期时间
selectMember.setAccessToken(selectMember.getAccessToken());
selectMember.setExpiresIn(selectMember.getExpiresIn());
baseMapper.updateById(selectMember);
return selectMember;
} else {
// 获取授权用户的信息存入自己的数据库
MemberEntity registMember = new MemberEntity();
registMember.setUsername((String) userInfoJson.get("name"));
registMember.setLevelId(levelService.getDefaultLevel().getId());
registMember.setGiteeUid(userInfoJson.getString("id"));
registMember.setAccessToken(loginVo.getAccess_token());
registMember.setExpiresIn(loginVo.getExpires_in());
baseMapper.insert(registMember);
return registMember;
}
}

MemberController

1
2
3
4
5
6
7
8
9
10
11
/**
* gitee会员登录
*/
@PostMapping("/giteelogin")
public R loginOAuth(@RequestBody GiteeUserVo loginVo, JSONObject userInfoJson) {
MemberEntity member = memberService.giteeLogin(loginVo, userInfoJson);
if (member == null) {
return R.error(BizCodeEnum.LOGIN_FAILED_EXCEPTION.getCode(), BizCodeEnum.LOGIN_FAILED_EXCEPTION.getMsg());
}
return R.ok().put("member", member);
}

MemberFeignService

1
2
3
4
5
6
7
8
@FeignClient("gulimall-member")
public interface MemberFeignService {
/**
* gitee会员登录
*/
@PostMapping("/member/member/giteelogin")
R loginOAuth(@RequestBody GiteeUserVo loginVo, JSONObject userInfoJson);
}

OAuth2Controller

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
@GetMapping("/oauth2/gitee/success")
public String gitee(@RequestParam("code") String code, RedirectAttributes attributes) throws Exception {
// 根据code授权码获取token
Map<String, String> map = new HashMap<>();
map.put("grant_type", "authorization_code");
map.put("code", code);
map.put("client_id", "");
map.put("redirect_uri", "http://auth.gulimall.com/oauth2/gitee/success");
map.put("client_secret", "");
HttpResponse response = HttpUtils.doPost("https://gitee.com", "/oauth/token", "post", new HashMap<>(), new HashMap<>(), map);
// 根据token获取用户信息
if (response.getStatusLine().getStatusCode() == 200) {
// 获取成功
String json = EntityUtils.toString(response.getEntity());
GiteeUserVo giteeUserVo = JSON.parseObject(json, GiteeUserVo.class);

// 如果该用户第一次进入,则自动生成账号,否则登录
HttpResponse userInfoResponse = HttpUtils.doPost("https://gitee.com", "/api/v5/user", "get", new HashMap<>(), new HashMap<>(), new HashMap<String, String>().put("access_token", giteeUserVo.getAccess_token()));
if (userInfoResponse.getStatusLine().getStatusCode() == 200) {
JSONObject userInfoJson = JSON.parseObject(EntityUtils.toString(userInfoResponse.getEntity()));
R r = memberFeignService.loginOAuth(giteeUserVo, userInfoJson);
if (r.getCode() == 0) {
// 获得登录后的member对象
MemberEntityVo member = (MemberEntityVo) r.get("member");
log.info("登陆成功,用户:{}", member.toString());
return "redirect:http://gulimall.com";
}
}
}
Map<String, String> errors = new HashMap<String, String>();
errors.put("msg", "授权登录失败");
attributes.addFlashAttribute("errors", errors);
return "redirect:http://auth.gulimall.com/login.html";
}

TODO gitee根据token获取userInfo被拒绝了,405not allowed,不清楚为啥,但是postman就能行,可能和httpClient有关

TODO post请求不能多个requestbody,所以我把获取userinfo放到了memberservice中,只需要传一个giteeUserVo即可

认证服务 - 登录判断

使用session,如果不用session的话参考上一个项目,使用jwt token

分布式下的session

分布式下session问题

image-20211119135441782

image-20211119135511743

分布式下session解决方法

  1. session复制
  2. 客户端存储
  3. hash一致性
  4. 统一存储
  5. 不同服务,子域session共享

image-20211119135535102

image-20211119135702556

image-20211119135713584

image-20211119135721385

image-20211119135732325

SpringSession

Spring Session - Spring Boot :: Spring Session

依赖 配置

1
2
3
4
5
6
7
<dependencies>
<!-- spring session -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
</dependencies>
1
2
3
4
spring.session.store-type=redis # Session store type.
spring.redis.host=localhost # Redis server host.
spring.redis.password= # Login password of the redis server.
spring.redis.port=6379 # Redis server port.
1
2
@EnableRedisHttpSession // 开启spring session
public class GulimallAuthServerApplication {

简单使用

1
2
3
4
5
6
// r取出来的值为map类型,map -> json -> bean
String memberJSON = JSONObject.toJSONString(r.get("member"));
MemberEntityVo memberEntityVo = JSON.parseObject(memberJSON, MemberEntityVo.class);
log.info("登陆成功,用户:{}", memberJSON);
// 保存用户信息至session,注意这里对象的类必须实现序列化,才能保存到redis中
session.setAttribute("loginUser", memberEntityVo);

Cannot deserialize; nested exception is org.springframework.core.serializer.support.SerializationFailedException:

清空redis

自定义SpringSession配置

用于放大作用域,使得跨域的时候还能使用session中的内容

放到common里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Configuration
@EnableRedisHttpSession // 开启spring session
public class MySessionConfig {

/*
* 配置cookie的相关信息:作用域和名字
*/
@Bean
public CookieSerializer cookieSerializer() {
DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
cookieSerializer.setDomainName("gulimall.com");
cookieSerializer.setCookieName("GULIMALL_SESSION");
return cookieSerializer;
}

@Bean
public RedisSerializer<Object> redisSerializer() {
GenericJackson2JsonRedisSerializer redisSerializer = new GenericJackson2JsonRedisSerializer();
return redisSerializer;
}

}

原理

@EnableRedisHttpSession导入RedisHttpSessionConfiguration配置

  1. 给容器中添加了一个组件
    SessionRepository ==> RedisOperationsSessionRepository ==> redis操作session。session的增删改查封装类
  2. SessionRepositoryFilter ==> Filter: session’存储过滤器;每个请求过来都必须经过filter
    1. 在request中存入SessionRepository
    2. request,response都被包装。SessionRepositoryRequestWrapper,SessionRepositoryResponseWrapper (装饰者模式)
    3. 放行filter放的是包装过的请求响应
    4. 获取session使用request.getSession() //即包装后的SessionRepositoryRequestWrapper
    5. wrappedRequest.getSession(); ==> 是重写的方法,先获取SessionRepository ,从SessionRepository 中获取之前存入的session内容。

登录判断

记得把webConfig中的映射关了

1
2
3
4
5
6
7
8
9
/*
* 前往登录页前判断是否已经登录
*/
@GetMapping("/login.html")
public String loginPage(HttpSession session) {
// 如果登陆过了就跳过
Object attribute = session.getAttribute(AuthServerConstant.LOGIN_USER);
return attribute == null ? "login" : "redirect:http://gulimall.com";
}

TODO 这里要去别的模块重复spring session的操作(所以thymeleaf不行,这些都是抽取出来好)

单点登录

多系统下只需一次登录

开源框架 xxl-sso

xxl-sso: 一个分布式单点登录框架。只需要登录一次就可以访问所有相互信任的应用系统。 拥有”轻量级、分布式、跨域、Cookie+Token均支持、Web+APP均支持”等特性;。现已开放源代码,开箱即用。 (gitee.com)

  1. 配置
  2. 打包:
    xxl-sso-master>mvn clean package -Dmaven.skip.test=true
  3. 清楚包:
    xxl-sso-master>mvn clean package -Dmaven.skip.test=true
  4. 启动服务:
    xxl-sso-master\xxl-sso-server\target>java -jar xxl-sso-server-1.1.1-SNAPSHOT.jar

购物车

环境搭建

  1. 模块
  2. host
  3. template、静态资源
  4. 网关
  5. 端口、注册中心

数据模型分析

  1. 登录、未登录购物车(duck不必)
  2. 登录后会将临时购物车的数据合并到登录后的账号中
  3. 临时购物车的数据在浏览器重启后仍存在
  4. 采用redis

image-20211120111700125

CartItemVo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Data
public class CartItemVo {
private Long skuId;
private Boolean check = true;
private String title;
private String image;
private List<String> skuAttr;
private BigDecimal price;
private Integer count;
private BigDecimal totalPrice;

public BigDecimal getTotalPrice() {
return this.price.multiply(new BigDecimal(this.count));
}
}

CartVo

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
@Data
public class CartVo {
private List<CartItemVo> items;
private Integer countNum; // 商品总数
private Integer countType; // 商品类型数
private BigDecimal totalPrice; // 总原价格价-优惠价
private BigDecimal reduce = new BigDecimal(0); // 优惠价

public Integer getCountNum() {
int count = 0;
if (items != null && items.size() > 0) {
for (CartItemVo item : items) {
count += item.getCount();
}
}
return count;
}

public Integer getCountType() {
return items.size();
}

public BigDecimal getTotalPrice() {
BigDecimal price = new BigDecimal(0);
if (items != null && items.size() > 0) {
for (CartItemVo item : items) {
if (item.getCheck()) {
price = price.add(item.getTotalPrice());
}
}
}
price = price.subtract(getReduce());
return price;
}
}

ThreadLocal用户身份鉴别

同一个线程共享数据

CartWebConfig

1
2
3
4
5
6
7
8
9
10
11
@Configuration
public class CartWebConfig implements WebMvcConfigurer {
/*
* 添加拦截器
* 配置拦截地址
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new CartInterceptor()).addPathPatterns("/**");
}
}

CartInterceptor

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
/*
* 判断登录状态,并封装传递给controller
*/
@Component
public class CartInterceptor implements HandlerInterceptor {

public static ThreadLocal<UserInfoTo> threadLocal = new ThreadLocal<>();

/*
* 在方法执行之前拦截
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
UserInfoTo userInfoTo = new UserInfoTo();
// 存入user-key
Cookie[] cookies = request.getCookies();
if (cookies != null && cookies.length > 0) {
for (Cookie cookie : cookies) {
// 判断当前cookie是否为user-key,是则存入ThreadLocal
if (cookie.getName().equals(AuthServerConstant.TEMP_USER_COOKIE_NAME)) {
userInfoTo.setUserKey(cookie.getValue());
// 当前cookie存在user-key
userInfoTo.setAlreadySet(true);
break;
}
}
}
// 如果没有user-key,保存一个临时用户
if (StringUtils.isEmpty(userInfoTo.getUserKey())) {
String uuid = UUID.randomUUID().toString();
userInfoTo.setUserKey(uuid);
}
// 如果登陆了存入userId
MemberEntityVo loginUser = (MemberEntityVo) request.getSession().getAttribute(AuthServerConstant.LOGIN_USER);
if (loginUser != null) {
// 已登录
userInfoTo.setUserId(loginUser.getId());
}
// 将信息存入threadLocal
threadLocal.set(userInfoTo);
return true;
}

/*
* 业务执行之后
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// 只存一次cookie即可
if (!threadLocal.get().isAlreadySet()) {
// 保存一个cookie,存储user-key,时间一个月
Cookie cookie = new Cookie(AuthServerConstant.TEMP_USER_COOKIE_NAME, threadLocal.get().getUserKey());
cookie.setDomain(ServerConstant.COOKIE_SERVER_DOMAIN);
cookie.setMaxAge(ServerConstant.COOKIE_MAX_AGE);
response.addCookie(cookie);
}
}
}

添加商品

SkuInfoFeignService

1
2
3
4
5
6
7
8
@FeignClient("gulimall-product")
public interface SkuInfoFeignService {
@RequestMapping("/product/skuinfo/info/{skuId}")
R info(@PathVariable("skuId") Long skuId);

@RequestMapping("/product/skusaleattrvalue/list/{skuId}")
List<String> getSkuSaleAttrList(@PathVariable("skuId") String skuId);
}

SkuSaleAttrValueDao

1
2
3
4
5
<select id="getSkuSaleAttrList" resultType="java.lang.String">
select concat(attr_name, ":", attr_value)
from gulimall_pms.pms_sku_sale_attr_value
where sku_id = #{skuId};
</select>

CartController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Controller
public class CartController {

@Autowired
CartService cartService;

/*
* 添加商品到购物车
*/
@GetMapping("/addToCart")
public String addToCart(@RequestParam("skuId") Long skuId, @RequestParam("num") Integer num, Model model) {
CartItemVo cartItemVo = cartService.addToCart(skuId, num);
model.addAttribute("item", cartItemVo);
return "success";
}

CartServiceImpl

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
@Service
@Slf4j
public class CartServiceImpl implements CartService {

@Autowired
StringRedisTemplate redisTemplate;
@Autowired
SkuInfoFeignService skuInfoFeignService;

@Autowired
ThreadPoolExecutor executor;

/*
* 添加商品
*/
@Override
public CartItemVo addToCart(Long skuId, Integer num) throws ExecutionException, InterruptedException {
BoundHashOperations<String, Object, Object> cartOptions = getCartOptions();

String existSku = (String) cartOptions.get(skuId.toString());
if (StringUtils.isEmpty(existSku)) {
CartItemVo cartItemVo = new CartItemVo();
// 当前购物城中没有该商品种类
CompletableFuture<Void> getSkuInfoTask = CompletableFuture.runAsync(() -> {
// 远程调用查询当前sku信息
R r = skuInfoFeignService.getSkuInfo(skuId);
if (r.getCode() == 0) {
SkuInfoTo skuInfo = r.getData("skuInfo", new TypeReference<SkuInfoTo>() {
});
// 将商品添加到购物车
cartItemVo.setCount(num);
cartItemVo.setImage(skuInfo.getSkuDefaultImg());
cartItemVo.setTitle(skuInfo.getSkuTitle());
cartItemVo.setPrice(skuInfo.getPrice());
cartItemVo.setSkuId(skuInfo.getSkuId());
} else {
log.info("远程调用失败");
}
}, executor);

CompletableFuture<Void> getAttrTask = CompletableFuture.runAsync(() -> {
// 获取当前sku的组合信息? 这个难道不是前端传吗,离谱
cartItemVo.setSkuAttr(skuInfoFeignService.getSkuSaleAttrList(skuId));
}, executor);

CompletableFuture.allOf(getAttrTask, getSkuInfoTask).get();
cartOptions.put(skuId.toString(), JSON.toJSONString(cartItemVo));
return cartItemVo;
} else {
// 已经有该商品
CartItemVo cartItemVo = JSON.parseObject(existSku, CartItemVo.class);
cartItemVo.setCount(cartItemVo.getCount() + num);
cartOptions.put(skuId.toString(), JSON.toJSONString(cartItemVo));
return cartItemVo;
}
}

/*
* 从redis中获取该购物车
*/
private BoundHashOperations<String, Object, Object> getCartOptions() {
String cartKey = ""; // redis中的购物车的key
UserInfoTo userInfoTo = CartInterceptor.threadLocal.get();
// 判断是否登录
if (userInfoTo.getUserId() != null) {
// 已登录
cartKey = CartConstant.CART_PREFIX + userInfoTo.getUserId();
} else {
// 未登录
cartKey = CartConstant.CART_PREFIX + userInfoTo.getUserKey();
}
return redisTemplate.boundHashOps(cartKey);
}
}

添加商品刷新防止多次添加

CartController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*
* 添加商品到购物车
*/
@GetMapping("/addToCart")
public String addToCart(@RequestParam("skuId") Long skuId, @RequestParam("num") Integer num, RedirectAttributes attributes) throws ExecutionException, InterruptedException {
CartItemVo cartItemVo = cartService.addToCart(skuId, num);
// 使用RedirectAttributes,防止使用model(model是request域)后重定向数据丢失
// addAttribute能够取多次,addFlashAttribute只能取一次
attributes.addAttribute("skuId", skuId);
// 防止页面刷新,所以到success页面应该是查操作,再过一个请求即可
return "redirect:http://cart.gulimall.com/addToCartSuccess.html";
}

// 跳转到成功页面
@GetMapping("/addToCartSuccess.html")
public String addToCartSuccessPage(@RequestParam("skuId") Long skuId, Model model) {
// 查询购物车中的skuId对应信息
CartItemVo cartItemVo = cartService.getCatItem(skuId);
model.addAttribute("item", cartItemVo);
return "success";
}

CartServiceImpl

1
2
3
4
5
6
7
8
9
10
/*
* 获取购物车商品信息
*/
@Override
public CartItemVo getCatItem(Long skuId) {
BoundHashOperations<String, Object, Object> cartOptions = getCartOptions();
String cartItemJSON = (String) cartOptions.get(skuId.toString());
CartItemVo cartItemVo = JSON.parseObject(cartItemJSON, CartItemVo.class);
return cartItemVo;
}

获取、合并购物车

cartListPage

1
2
3
4
5
6
7
8
@RequestMapping("/cart.html")
public String cartListPage(Model model) throws ExecutionException, InterruptedException {
// 通过threadLocal获得当前用户信息
// UserInfoTo userInfoTo = CartInterceptor.threadLocal.get();
CartEntity cart = cartService.getCart();
model.addAttribute("cart", cart);
return "cartList";
}

CartServiceImpl

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
/*
* 获得购物车信息
*/
@Override
public CartEntity getCart() throws ExecutionException, InterruptedException {
CartEntity cart = new CartEntity();
BoundHashOperations<String, Object, Object> cartFromRedis = getCartFromRedis();

// 判断是否登录
UserInfoTo userInfoTo = CartInterceptor.threadLocal.get();
List<CartItemEntity> cartItemByUserKey = getCartItem(CartConstant.CART_PREFIX + userInfoTo.getUserKey());
if (userInfoTo.getUserId() != null) {
// 已登录
// 合并购物车(使用addToCart带有同商品的判断)
for (CartItemEntity cartItem : cartItemByUserKey) {
addToCart(cartItem.getSkuId(), cartItem.getCount());
}
List<CartItemEntity> cartItemByUserId = getCartItem(CartConstant.CART_PREFIX + userInfoTo.getUserId());
// redis中清空临时购物车信息
redisTemplate.delete(CartConstant.CART_PREFIX + userInfoTo.getUserKey());
// 返回购物车的信息
cart.setItems(cartItemByUserId);
} else {
// 未登录
// 返回临时购物车的信息
cart.setItems(cartItemByUserKey);
}
return cart;
}

/*
* 根据xxx获取购物车商品list
*/
private List<CartItemEntity> getCartItem(String cartKey) {
// 从redis中根据userId获得cartItem
UserInfoTo userInfoTo = CartInterceptor.threadLocal.get();
BoundHashOperations<String, Object, Object> cartMap = redisTemplate.boundHashOps(cartKey);
// 封装list
List<CartItemEntity> cartItemList = cartMap.values().stream().map((cartItemObj) -> {
return JSON.parseObject(cartItemObj.toString(), CartItemEntity.class);
}).collect(Collectors.toList());
return cartItemList;
}

/*
* 从redis中获取该购物车
*/
private BoundHashOperations<String, Object, Object> getCartFromRedis() {
String cartKey = ""; // redis中的购物车的key
UserInfoTo userInfoTo = CartInterceptor.threadLocal.get();
// 判断是否登录
if (userInfoTo.getUserId() != null) {
// 已登录
cartKey = CartConstant.CART_PREFIX + userInfoTo.getUserId();
} else {
// 未登录
cartKey = CartConstant.CART_PREFIX + userInfoTo.getUserKey();
}
return redisTemplate.boundHashOps(cartKey);
}

选中、改变数量、删除

这里最好用一个完整的VO+ajax

CartController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*
* 更改购物车选中状态
*/
@PostMapping("/checkItem")
public String checkItem(@RequestParam("skuId") Long skuId, @RequestParam("check") Integer check) {
cartService.checkItem(skuId, check);
return "redirect:http://cart.gulimall.com/cart.html";
}
/*
* 更改购物车商品数
*/
@GetMapping("/countItem")
public String countItem(@RequestParam("skuId") Long skuId, @RequestParam("num") Integer num) {
cartService.countItem(skuId, num);
return "redirect:http://cart.gulimall.com/cart.html";
}
/*
* 更改购物车商品数
*/
@GetMapping("/deleteItem")
public String deleteItem(@RequestParam("skuId") Long skuId) {
cartService.deleteItem(skuId);
return "redirect:http://cart.gulimall.com/cart.html";
}

CartServiceImpl

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
/*
* 修改商品check
*/
@Override
public void checkItem(Long skuId, Integer check) {
CartItemEntity cartOneItem = getCartOneItem(skuId);
cartOneItem.setCheck(check == 1);
updateCartOneItem(cartOneItem);
}
/*
* 修改商品count
*/
@Override
public void countItem(Long skuId, Integer num) {
CartItemEntity cartOneItem = getCartOneItem(skuId);
cartOneItem.setCount(num);
updateCartOneItem(cartOneItem);
}
/*
* redis删除商品
*/
@Override
public void deleteItem(Long skuId) {
BoundHashOperations<String, Object, Object> cartOptions = getCartFromRedis();
cartOptions.delete(skuId.toString());
}
/*
* 修改redis单个购物车商品信息
*/
@Override
public void updateCartOneItem(CartItemEntity cartItemEntity) {
BoundHashOperations<String, Object, Object> cartOptions = getCartFromRedis();
String json = JSON.toJSONString(cartItemEntity);
cartOptions.put(cartItemEntity.getSkuId().toString(), json);
}

评论




🧡💛💚💙💜🖤🤍