0%

一些微服务项目开发中遇到的问题

如何理解SpringCloud中常用组件之间的配合使用?

使用SpringCloud Gateway时,无法使用Spring Session

  1. spring cloud gateway是基于webflux,是非阻塞的,zuul是基于servlet的,是阻塞的,所以在gateway中需要将EnableRedisHttpSession注解换成EnableRedisWebSession:

    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
    @EnableRedisWebSession(saveMode = SaveMode.ALWAYS)
    @Configuration
    public class RedisSessionConfig {
    @Bean
    public WebSessionIdResolver webSessionIdResolver() {
    return new CustomWebSessionIdResolver();
    }

    }
    class CustomWebSessionIdResolver extends CookieWebSessionIdResolver {
    // 重写resolve方法 对SESSION进行base64解码
    @Override
    public List<String> resolveSessionIds(ServerWebExchange exchange) {
    MultiValueMap<String, HttpCookie> cookieMap = exchange.getRequest().getCookies();
    // 获取SESSION
    List<HttpCookie> cookies = cookieMap.get(getCookieName());
    if (cookies == null) {
    return Collections.emptyList();
    }
    return cookies.stream().map(HttpCookie::getValue).map(this::base64Decode).collect(Collectors.toList());
    }

    private String base64Decode(String base64Value) {
    try {
    byte[] decodedCookieBytes = Base64.getDecoder().decode(base64Value);
    return new String(decodedCookieBytes);
    } catch (Exception ex) {
    return null;
    }
    }
    }
  2. 实现GlobalFilter接口,例如:登录过滤功能

    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
    @Component
    public class SCWAccessFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    ServerHttpRequest request = exchange.getRequest();
    String path = request.getPath().toString();
    if (AccessPassResources.PASS_RES_SET.contains(path)) {
    return chain.filter(exchange);
    }
    if (AccessPassResources.currentServletPathIsStaticResource(path)) {
    return chain.filter(exchange);
    }
    AtomicBoolean loginSuccess = new AtomicBoolean(false);
    exchange.getSession().subscribe(webSession -> {
    Object loginMember = webSession.getAttribute(SCWConstant.ATTR_NAME_LOGIN_MEMBER);
    if(loginMember!=null){
    loginSuccess.set(true);
    }else {
    exchange.getResponse();
    webSession.getAttributes().put(SCWConstant.ATTR_NAME_MESSAGE, SCWConstant.MESSAGE_ACCESS_FORBIDDEN);
    loginSuccess.set(false);
    }
    });
    try {
    Thread.sleep(200);
    } catch (InterruptedException e) {
    throw new RuntimeException(e);
    }
    if (loginSuccess.get()) {
    return chain.filter(exchange);
    }
    ServerHttpResponse response = exchange.getResponse();
    response.setStatusCode(HttpStatus.SEE_OTHER);
    response.getHeaders().set(HttpHeaders.LOCATION, "/login.html");
    response.getHeaders().add("Content-Type", "text/plain;charset=UTF-8");
    return response.setComplete();
    }

    @Override
    public int getOrder() {
    return -1;
    }
    }

使用SpringCloud Gateway时,如果对应请求的返回结果是重定向,则会改变主机地址为对应微服务的地址(302)?

解决:修改application.yml配置文件PreserveHostHeader,对请求头进行处理

1
2
3
4
5
6
7
8
9
10
spring:
cloud:
gateway:
routes:
- id: scw-auth
uri: lb://SCW-AUTH
predicates:
- Path=/**
filters:
- PreserveHostHeader

邮箱验证码功能

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
public class ProjectUtils {
/**
* @param fromEmail 发送者邮箱
* @param licenseCode 发送者邮箱登录凭证
* @param receiveEmail 接收者邮箱
* @param codeLength 验证码长度
* @param name 收件人名称
* @return
* @throws MessagingException
* @throws UnsupportedEncodingException
*/
public static String sendEmail(String fromEmail, String licenseCode, String receiveEmail, int codeLength, String name) throws MessagingException, UnsupportedEncodingException {
Properties props = new Properties();
props.setProperty("mail.transport.protocol", "smtp"); //使用smpt的邮件传输协议
props.setProperty("mail.smtp.host", "smtp.qq.com"); //主机地址
props.setProperty("mail.smtp.auth", "true"); //授权通过

Session session = Session.getInstance(props); //通过我们的这些配置,得到一个会话程序
MimeMessage message = new MimeMessage(session);
message.setFrom(new InternetAddress(fromEmail)); //设置发件人
message.setRecipient(MimeMessage.RecipientType.TO, new InternetAddress(receiveEmail, "用户", "utf-8")); //设置收件人
message.setSubject("邮件验证码", "utf-8"); //设置主题
message.setSentDate(new Date());
StringBuilder code = new StringBuilder();
for (int i = 0; i < codeLength; i++) {
code.append((int) (Math.random() * 10));
}
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
String str = "<!DOCTYPE html><html><head><meta charset='UTF-8'></head><body><p style='font-size: 20px;font-weight:bold;'>尊敬的" + name + ":您好!</p>"
+ "<p style='text-indent:2em; font-size: 20px;'>欢迎注册,您本次的注册码是 "
+ "<span style='font-size:30px;font-weight:bold;color:red'>" + code + "</span>,1分钟之内有效,请尽快使用!</p>"
+ "<p style='text-align:right; padding-right: 20px;'"
+ "<span style='font-size: 18px; float:right; margin-right: 30px;'>" + sdf.format(new Date()) + "</span></body></html>";

Multipart mul = new MimeMultipart(); //新建一个MimeMultipart对象来存放多个BodyPart对象
BodyPart mdp = new MimeBodyPart(); //新建一个存放信件内容的BodyPart对象
mdp.setContent(str, "text/html;charset=utf-8");
mul.addBodyPart(mdp); //将含有信件内容的BodyPart加入到MimeMultipart对象中
message.setContent(mul); //把mul作为消息内容


message.saveChanges();

//创建一个传输对象
Transport transport = session.getTransport("smtp");

//建立与服务器的链接 465端口是 SSL传输
transport.connect("smtp.qq.com", 587, fromEmail, licenseCode);

//发送邮件
transport.sendMessage(message, message.getAllRecipients());

//关闭邮件传输
transport.close();

return code.toString();

}
}

OpenFeign远程调用报错

错误内容:

### Error updating database. Cause: java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '' at line 1 ### The error may exist in file [D:-member-parent-member-mysql-.xml] ### The error may involve com.zephon.scw.mapper.MemberPOMapper.insertSelective-Inline ### The error occurred while setting parameters ### SQL: insert into t_member ### Cause: java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '' at line 1 ; bad SQL grammar []; nested exception is java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '' at line 1

解决:

在使用OpenFeign接口时,服务提供者的请求参数中忘写@RequestBody注解了,之后开发中最好将服务提供者和OpenFeign接口对应的方法请求参数需要写的都写上@RequestBody

服务提供者:

1
2
@RequestMapping("/save/member/remote")
public ResultEntity<MemberPO> saveMemberPO(@RequestBody MemberPO memberPO){

接口:

1
2
3
4
5
@FeignClient("scw-mysql")
public interface MySQLRemoteService {
@RequestMapping("/save/member/remote")
ResultEntity<MemberPO> saveMemberPO(@RequestBody MemberPO memberPO);
}

MyBatis插入数据后如何获取生成的ID?

  1. 配置Mapper的XML,useGeneratedKeys="true" keyProperty="xxx"如:

    1
    <insert id="insertSelective" useGeneratedKeys="true" keyProperty="id" ...
  2. 插入后通过get方法获取生成的id

异步请求中无法对session中的属性进行更新?暂未找到原因,只能侧面解决

问题:

在请求A中设置了Session

1
2
3
4
5
6
@RequestMapping("/A")
public String saveProjectBasicInfo(ProjectVO projectVO,
HttpSession session,
ModelMap modelMap) throws IOException {
...
session.setAttribute(SCWConstant.ATTR_NAME_TEMPLE_PROJECT, projectVO);

@ResponseBody请求中更新Session:

1
2
3
4
5
6
@ResponseBody
@RequestMapping("/B")
public ResultEntity<String> save(ReturnVO returnVO, HttpSession session) {
ProjectVO projectVO = (ProjectVO) session.getAttribute(SCWConstant.ATTR_NAME_TEMPLE_PROJECT);
projectVO.setReturnVOList(new ArrayList<>());
session.setAttribute(SCWConstant.ATTR_NAME_TEMPLE_PROJECT, projectVO);

在请求C中获取session中的数据,发现之前设置的值是空的

1
2
3
4
@RequestMapping("/C")
public String createConfirm(HttpSession session){
ProjectVO projectVO = (ProjectVO) session.getAttribute(SCWConstant.ATTR_NAME_TEMPLE_PROJECT);
projectVO.getReturnVOList(); // 发现是null

解决方案:未真正解决

猜测:Gateway的问题

侧面解决,换个名

在请求B中将要添加的属性单独放到session中

1
2
3
4
5
6
@ResponseBody
@RequestMapping("/B")
public ResultEntity<String> save(ReturnVO returnVO, HttpSession session) {
ProjectVO projectVO = (ProjectVO) session.getAttribute(SCWConstant.ATTR_NAME_TEMPLE_PROJECT);
projectVO.setReturnVOList(new ArrayList<>());
session.setAttribute("new_name", projectVO);

然后在请求C中再去获得并设置对应的属性:

1
2
3
@RequestMapping("/C")
public String createConfirm(HttpSession session){
ProjectVO projectVO = (ProjectVO) session.getAttribute("new_name");

正式解决方案:

之前为了解决Gateway无法通过session判断登录的问题,对Gateway进行了配置:

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
@EnableRedisWebSession(saveMode = SaveMode.ALWAYS)
@Configuration
public class RedisSessionConfig {
@Bean
public WebSessionIdResolver webSessionIdResolver() {
return new CustomWebSessionIdResolver();
}

}
class CustomWebSessionIdResolver extends CookieWebSessionIdResolver {
// 重写resolve方法 对SESSION进行base64解码
@Override
public List<String> resolveSessionIds(ServerWebExchange exchange) {
MultiValueMap<String, HttpCookie> cookieMap = exchange.getRequest().getCookies();
// 获取SESSION
List<HttpCookie> cookies = cookieMap.get(getCookieName());
if (cookies == null) {
return Collections.emptyList();
}
return cookies.stream().map(HttpCookie::getValue).map(this::base64Decode).collect(Collectors.toList());
}

private String base64Decode(String base64Value) {
try {
byte[] decodedCookieBytes = Base64.getDecoder().decode(base64Value);
return new String(decodedCookieBytes);
} catch (Exception ex) {
System.out.println("Unable to Base64 decode value: " + base64Value);
return null;
}
}
}

这就会导致Session无法更新的问题,所以将其注释后就可以解决这个问题,然后只能通过token对登录进行认证

在创建一个微服务去调用Feign接口时,直接从另一个服务复制过来的包和配置文件进行修改的,但调用Feign接口报找不到Bean错误

  1. 检查Main类上面是否有@EnableFeignClients注解
  2. 个人遇到问题的原因是IDEA复制包时没有复制多级包,导致我的controller在abc目录下,而Feign接口在com.zephon.abc包下,因此,需要再建一个对应的多级包即可