功能实现
1.用户登录
1.1阿里云短信
1.1.1初体验
- 官方网站:https://www.aliyun.com/product/sms?spm=5176.19720258.J_8058803260.611.48192c4abPvXEp
- 阿里云短信-申请
- 注册阿里云账号
- 开通短信服务
- 申请签名
- 配置模板
- 示例代码测试
代码测试
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
36package cn.itheima.test;
import com.aliyun.dysmsapi20170525.models.SendSmsRequest;
import com.aliyun.dysmsapi20170525.models.SendSmsResponse;
import com.aliyun.dysmsapi20170525.models.SendSmsResponseBody;
import com.aliyun.teaopenapi.models.Config;
public class Sample {
public static void main(String[] args_) throws Exception {
//配置阿里云
Config config = new Config()
//AccessKey ID
.setAccessKeyId("ID")
//AccessKey Secret
.setAccessKeySecret("Key");
//访问域名
config.endpoint = "dysmsapi.aliyuncs.com";
com.aliyun.dysmsapi20170525.Client client = new com.aliyun.dysmsapi20170525.Client(config);
SendSmsRequest sendSmsRequest = new SendSmsRequest()
.setSignName("ABC商城")
.setTemplateCode("SMS_467595081")
.setTemplateParam("{\"code\":\"1234\"}")
.setPhoneNumbers("手机");
// 复制代码运行请自行打印 API 的返回值
SendSmsResponse response = client.sendSms(sendSmsRequest);
SendSmsResponseBody body = response.getBody();
}
}
代码模板
1 | public static void main(String[] args_) throws Exception { |
1.1.2组件抽取
- 企业开发中,往往将常见工具类封装抽取,以简洁便利的方式供其他工程模块使用。而SpringBoot的自动装配机制可以方便的实现组件抽取。SpringBoot执行流程如下:
- 扫描依赖模块中META-INF/spring.factories
- 执行装配类中方法
- 对象存入容器中
- 核心工程注入对象,调用方法使用
验证码的抽取
- tanhua-autoconfig
- 在 tanhua-autoconfig 定义模板对象SmsTemplate
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
35package com.tanhua.autoconfig.template;
import com.aliyun.dysmsapi20170525.models.SendSmsRequest;
import com.aliyun.dysmsapi20170525.models.SendSmsResponse;
import com.aliyun.dysmsapi20170525.models.SendSmsResponseBody;
import com.aliyun.teaopenapi.models.Config;
public class SmsTemplate {
public void sendSms(String mobile,String code){
try {
//配置阿里云
Config config = new Config()
//AccessKey ID
.setAccessKeyId("id")
//AccessKey Secret
.setAccessKeySecret("secret");
//访问域名
config.endpoint = "dysmsapi.aliyuncs.com";
com.aliyun.dysmsapi20170525.Client client = new com.aliyun.dysmsapi20170525.Client(config);
SendSmsRequest sendSmsRequest = new SendSmsRequest()
.setSignName("ABC商城")
.setTemplateCode("SMS_467595081")
.setTemplateParam("{\"code\":\""+code+"\"}")
.setPhoneNumbers(mobile);
// 复制代码运行请自行打印 API 的返回值
SendSmsResponse response = client.sendSms(sendSmsRequest);
SendSmsResponseBody body = response.getBody();
}catch (Exception e){
e.printStackTrace();
}
}
} - 在 tanhua-autoconfig 定义配置类TanhuaAutoConfigration
1
2
3
4
5
6
7
8
9
10
11
12package com.tanhua.autoconfig;
import com.tanhua.server.SmsTemplate;
import org.springframework.context.annotation.Bean;
public class TanhuaAutoConfigration {
public SmsTemplate smsTemplate(){
return new SmsTemplate();
}
} - 在 tanhua-autoconfig 中添加装配文件 META-INF/spring.factories
1
2org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.tanhua.autoconfig.TanhuaAutoConfiguration
- 在 tanhua-autoconfig 定义模板对象SmsTemplate
- tanhua-app-server
- 添加tanhua-autoconfig工程的依赖
- 添加启动类
- 在yaml文件中添加访问名称
- 创建SmsTemplateTest进行测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23package cn.itheima.test;
import com.tanhua.server.AppServerApplication;
import com.tanhua.server.SmsTemplate;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
public class SmsTemplateTest {
//注入
private SmsTemplate smsTemplate;
//测试
public void testSendSms(){
smsTemplate.sendSms("15018398130","4567");
}
}
改进代码(消息以硬编码形式写入)
- tanhua-app-server工程加入短信配置
1
2
3
4
5
6tanhua:
sms:
signName: 物流云商
templateCode: SMS_106590012
accessKey: key
secret: secret - 在tanhua-autoconfig创建配置信息类
1
2
3
4
5
6
7
8
9
10
11
12
13
14package com.tanhua.autoconfig.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
public class Smsproperties {
private String signName;
private String templateCode;
private String accessKey;
private String secret;
} - 修改SmsTemplate方法
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
44package com.tanhua.autoconfig.template;
import com.aliyun.dysmsapi20170525.models.SendSmsRequest;
import com.aliyun.dysmsapi20170525.models.SendSmsResponse;
import com.aliyun.dysmsapi20170525.models.SendSmsResponseBody;
import com.aliyun.teaopenapi.models.Config;
import com.tanhua.autoconfig.properties.Smsproperties;
public class SmsTemplate {
private Smsproperties properties;
private SmsTemplate(Smsproperties properties){
this.properties = properties;
}
public void sendSms(String mobile,String code){
try {
//配置阿里云
Config config = new Config()
//AccessKey ID
.setAccessKeyId(properties.getAccessKey())
//AccessKey Secret
.setAccessKeySecret(properties.getSecret());
//访问域名
config.endpoint = "dysmsapi.aliyuncs.com";
com.aliyun.dysmsapi20170525.Client client = new com.aliyun.dysmsapi20170525.Client(config);
SendSmsRequest sendSmsRequest = new SendSmsRequest()
.setSignName(properties.getSignName())
.setTemplateCode(properties.getTemplateCode())
.setTemplateParam("{\"code\":\""+code+"\"}")
.setPhoneNumbers(mobile);
// 复制代码运行请自行打印 API 的返回值
SendSmsResponse response = client.sendSms(sendSmsRequest);
SendSmsResponseBody body = response.getBody();
}catch (Exception e){
e.printStackTrace();
}
}
} - 修改TanhuaAutoConfiguration方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18package com.tanhua.autoconfig;
import com.tanhua.autoconfig.properties.Smsproperties;
import com.tanhua.autoconfig.template.SmsTemplate;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
public class TanhuaAutoConfiguration {
public SmsTemplate smsTemplate(Smsproperties properties){
return new SmsTemplate(properties);
}
}
1.2获取验证码
步骤概括
- 因为验证码直接用map来接值,所以不用实体类
- 定义LoginController
- 实现UserService —-直接调用自动配置类里面的API方法
- 配置yaml文件
1.2.1流程分析
- 客户端发送请求
- 服务端调用第三方组件发送验证码
- 验证码发送成功,存入redis
- 响应客户端,客户端跳转到输入验证码页面
1.2.2接口定义
1 | 接口路径:/user/login |
- LoginController —- APP(消费者)
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
27package com.tanhua.Controller;
import com.tanhua.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
public class LoginController {
private UserService userService;
/**
* 获取登录验证码
* 请求参数:phone (Map)
* 响应:void
*/
public ResponseEntity login({ Map map)
String phone = String.valueOf(map.get("phone"));
userService.sendMsg(phone);
return ResponseEntity.ok(null);//正常返回状态码200
}
}1.2.3接口实现
- 配置yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19server:
port: 18080
spring:
application:
name: tanhua-app-server
redis:
port: 6379
host: 192.168.136.160
cloud:
nacos:
discovery:
server-addr: 192.168.136.160:8848
tanhua:
sms:
signName: ABC商城
templateCode: SMS_467595081
accessKey: key
secret: secret - UserService —- APP(消费者)
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
30package com.tanhua.service;
import com.tanhua.SmsTemplate;
import org.apache.commons.lang.RandomStringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.time.Duration;
public class UserService {
private SmsTemplate smsTemplate;
private RedisTemplate redisTemplate;
/**
* 发送短信验证码
* @param phone
*/
public void sendMsg(String phone){
//1.随机生成6位数字
String code = RandomStringUtils.randomNumeric(6);
//2、调用template对象,发送手机短信
smsTemplate.sendSms(phone,code);
//3、将验证码存入到redis
redisTemplate.opsForValue().set("CHECK_CODE_"+phone,code, Duration.ofMinutes(5));
}
}1.2.4测试结果
1.3验证码校验
步骤概括
- 服务提供者
- 创建实体类 —-model模块
- 创建mapper接口 —-dubbo-db模块
- 配置API —-dubbo-interface模块
- 实现API —-dubbo-db模块
- 创建启动类
- 配置yaml
- 单元测试
- 定义LoginController —-app模块
- 完善UserService —-app模块
1.3.1 JWT
JWT介绍
- JSON Web token简称JWT, 是用于对应用程序上的用户进行身份验证的标记。
- JWT就是一个字符串,经过加密处理与校验处理的字符串,形式为:A.B.C
- A由JWT头部信息header加密得到
- B由JWT用到的身份验证信息json数据加密得到
- C由A和B加密得到,是校验部分
- 鉴权流程
JWT入门案例
- 导入依赖
1
2
3
4
5<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency> - 编写测试用例
- testCreateToken
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
54package cn.itheima.test;
import io.jsonwebtoken.*;
import org.junit.jupiter.api.Test;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class CreateTokenTest {
public void testCreateToken() {
//生成token
//1、准备数据
Map map = new HashMap();
map.put("id",1);
map.put("mobile","13800138000");
//2、使用JWT的工具类生成token
long now = System.currentTimeMillis();
String token = Jwts.builder()
.signWith(SignatureAlgorithm.HS512, "itcast") //指定加密算法
.setClaims(map) //写入数据
.setExpiration(new Date(now + 30000)) //失效时间
.compact();
System.out.println(token);
}
//解析token
/**
* SignatureException : token不合法
* ExpiredJwtException:token已过期
*/
public void testParseToken() {
String token = "eyJhbGciOiJIUzUxMiJ9.eyJtb2JpbGUiOiIxMzgwMDEzODAwMCIsImlkIjoxLCJleHAiOjE3MTY3ODk4NDV9.97ZmACuCs9JIsihHHvmWigRTKWRlCxkEyQHYIuvTzDW-zPj5Md9KUsJZvAcIYl2LSVonyie2D66cv_IUB0HH6Q";
try {
Claims claims = Jwts.parser()
.setSigningKey("itcast")
.parseClaimsJws(token)
.getBody();
Object id = claims.get("id");
Object mobile = claims.get("mobile");
System.out.println(id + "--" + mobile);
}catch (ExpiredJwtException e) {
System.out.println("token已过期");
}catch (SignatureException e) {
System.out.println("token不合法");
}
}
}
1.3.2需求分析
- tanhua-model配置实体类 —-model模块
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17package com.tanhua.model.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Date;
public class User implements Serializable {
private Long id;
private String mobile;
private String password;
private Date created;
private Date updated;
} - 创建数据库层Mapper接口 —-dubbo-db模块
1
2
3
4
5package com.tanhua.dubbo.mappers;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
public interface UserMapper extends BaseMapper<User> {
} - 配置API接口和实现类
- dubbo-interface模块
1
2
3
4
5
6
7
8package com.tanhua.dubbo.api;
import com.tanhua.dubbo.domain.User;
public interface UserApi {
//根据手机查询用户
User findByMobile(String mobile);
} - dubbo-db模块
1
2
3
4
5
6
7
public User findByMobile(String mobile) {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("mobile",mobile);
return userMapper.selectOne(queryWrapper);
}
- dubbo-interface模块
- 创建引导类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22package com.tanhua.dubbo.api;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.tanhua.dubbo.mapper.UserMapper;
import com.tanhua.dubbo.domain.User;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.beans.factory.annotation.Autowired;
public class UserApiImpl implements UserApi{
private UserMapper userMapper;
//根据手机号码查询
public User findByMobile(String mobile) {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("mobile",mobile);
return userMapper.selectOne(queryWrapper);
}
} - 添加application.yaml配置
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
29server:
port: 18081
spring:
application:
name: tanhua-dubbo-db
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/tanhua?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&useSSL=false
username: root
password: 123456
cloud:
nacos:
discovery:
server-addr: 192.168.136.160:8848
dubbo:
protocol:
name: dubbo
port: 20881
registry:
address: spring-cloud://localhost
scan:
base-packages: com.tanhua.dubbo.api #dubbo中包扫描
mybatis-plus:
global-config:
db-config:
table-prefix: tb_ # 表名前缀
id-type: auto # id策略为自增长 - 单元测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24package cn.itheima.test;
import com.tanhua.AppServerApplication;
import com.tanhua.dubbo.api.UserApi;
import com.tanhua.dubbo.domain.User;
import org.apache.dubbo.config.annotation.DubboReference;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
public class UserApiTest {
private UserApi userApi;
public void testFindByMobile(){
User user = userApi.findByMobile("13800138000");
System.out.println(user);
}
}
1.3.4接口定义
1 | 接口路径:/user/loginVerification |
- LoginController
1
2
3
4
5
6
7
8
9
10
11
12
13/**
* 检验登录
*/
public ResponseEntity loginVerification( { Map map)
//1、调用map集合获取请求参数
String phone = String.valueOf(map.get("phone"));
String code = String.valueOf(map.get("verificationCode"));
//2、调用userService完成用户登录
Map retMap = userService.loginVerification(phone,code);
//3、构造返回
return ResponseEntity.ok(retMap);
}
1.3.5接口实现
- 实体类大量属性重复—-把公共的属性提取出来,然后继承
1
2
3
4
5
6
7
8
9
10
11
12
13
14package com.tanhua.dubbo.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
public class User extends BasePojo{
private Long id;
private String mobile;
private String password;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18package com.tanhua.dubbo.domain;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
public abstract class BasePojo implements Serializable {
//自动填充
private Date created;
private Date updated;
} 保存或者更新数据库,频繁手动设置时间—-一次性使用@TableField配置自动填充属性字段
1
2
3
4
5
6
7
8
9
public abstract class BasePojo implements Serializable {
//自动填充
private Date created;
private Date updated;
}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
30package com.tanhua.dubbo.handler;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.util.Date;
public class MyMetaObjectHandler implements MetaObjectHandler {
public void insertFill(MetaObject metaObject) {
Object created = getFieldValByName("created", metaObject);
if (null == created) {
//字段为空,可以进行填充
setFieldValByName("created", new Date(), metaObject);
}
Object updated = getFieldValByName("updated", metaObject);
if (null == updated) {
//字段为空,可以进行填充
setFieldValByName("updated", new Date(), metaObject);
}
}
public void updateFill(MetaObject metaObject) {
//更新数据时,直接更新字段
setFieldValByName("updated", new Date(), metaObject);
}
}JWt工具类
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
61package com.tanhua.commons.utils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.apache.commons.lang3.StringUtils;
import java.util.Date;
import java.util.Map;
public class JwtUtils {
// TOKEN的有效期1小时(S)
private static final int TOKEN_TIME_OUT = 3_600;
// 加密KEY
private static final String TOKEN_SECRET = "itcast";
// 生成Token
public static String getToken(Map params){
long currentTime = System.currentTimeMillis();
return Jwts.builder()
.signWith(SignatureAlgorithm.HS512, TOKEN_SECRET) //加密方式
.setExpiration(new Date(currentTime + TOKEN_TIME_OUT * 1000)) //过期时间戳
.addClaims(params)
.compact();
}
/**
* 获取Token中的claims信息
*/
private static Claims getClaims(String token) {
return Jwts.parser()
.setSigningKey(TOKEN_SECRET)
.parseClaimsJws(token).getBody();
}
/**
* 是否有效 true-有效,false-失效
*/
public static boolean verifyToken(String token) {
if(StringUtils.isEmpty(token)) {
return false;
}
try {
Claims claims = Jwts.parser()
.setSigningKey("itcast")
.parseClaimsJws(token)
.getBody();
}catch (Exception e) {
return false;
}
return true;
}
}UserApi —-interface模块
1
2//保存用户,返回用户id
Long save(User user);UserApiImpl —-db模块
1
2
3
4
5
6//保存用户id
public Long save(User user) {
userMapper.insert(user);
return user.getId();
}UserService
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
87package com.tanhua.service;
import com.tanhua.commons.utils.JwtUtils;
import com.tanhua.dubbo.api.SmsTemplate;
import com.tanhua.dubbo.api.UserApi;
import com.tanhua.dubbo.domain.User;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
public class UserService {
private SmsTemplate template;
private RedisTemplate<String,String> redisTemplate;
private UserApi userApi;
/**
* 发送短信验证码
* @param phone
*/
public void sendMsg(String phone) {
//1、随机生成6位数字
//String code = RandomStringUtils.randomNumeric(6);
String code = "123456";
//2、调用template对象,发送手机短信
//template.sendSms(phone,code);
//3、将验证码存入到redis
redisTemplate.opsForValue().set("CHECK_CODE_"+phone,code, Duration.ofMinutes(5));
}
/**
* 验证登录
* @param phone
* @param code
*/
public Map loginVerification(String phone, String code) {
//1、从redis中获取下发的验证码
String redisCode = redisTemplate.opsForValue().get("CHECK_CODE_" + phone);
//2、对验证码进行校验(验证码是否存在,是否和输入的验证码一致)
if(StringUtils.isEmpty(redisCode) || !redisCode.equals(code)) {
//验证码无效
throw new RuntimeException("验证码错误");
}
//3、删除redis中的验证码
redisTemplate.delete("CHECK_CODE_" + phone);
//4、通过手机号码查询用户
User user = userApi.findByMobile(phone);
boolean isNew = false;
//5、如果用户不存在,创建用户保存到数据库中
if(user == null) {
user = new User();
user.setMobile(phone);
user.setPassword(DigestUtils.md5Hex("123456"));
Long userId = userApi.save(user);
user.setId(userId);
isNew = true;
}
//6、通过JWT生成token(存入id和手机号码)
Map tokenMap = new HashMap();
tokenMap.put("id",user.getId());
tokenMap.put("mobile",phone);
String token = JwtUtils.getToken(tokenMap);
//7、构造返回值
Map retMap = new HashMap();
retMap.put("token",token);
retMap.put("isNew",isNew);
return retMap;
}
}
1.3.6前后端联调
- 修改模拟器里面的API地址
- http://172.16.1.2:18080
- 172.16.1为模拟器的ip地址前3为
- 2 固定
- 18080 为端口号
1.4完善个人信息
1.4.1流程分析
- 首次登录时(手机号码不存在),需要创建用户存入数据库中
- 客户端检测首次登录需要完善用户信息
- 单一服务器存储
- 优点:开发便捷,成本低
- 缺点:扩容困难
- 分布式文件系统
- 优点:容易实现扩容
- 缺点:开发复杂度稍大(有成熟的产品可以使用,比如:FastDFS)
使用第三方的存储服务
- 优点:开发简单,拥有强大功能,免维护
- 缺点:付费
阿里云OSS
- 阿里云对象存储服务(Object Storage Service,OSS)是一种海量、安全、低成本、高可靠的云存储服务,适合存放任意类型的文件。容量和处理能力弹性扩展,多种存储类型供选择,全面优化存储成本。
- 官方地址:https://www.aliyun.com/product/oss
使用案例
- bucket: tanhua0001
需要自己配置id 和 secret
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
46package cn.itheima.test;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.UUID;
public class OssTest {
/**案例:
* 将资料中的default_cover_2.webp上传到阿里云OSS
* 存放位置: /yyyy/MM/dd/xxx.webp
*/
public void OssTest() throws FileNotFoundException {
//1.配置图片路径
String path = "C:\\Users\\19849\\Desktop\\新建文件夹\\default_cover_2.webp";
//2.构造FileInputStream
FileInputStream fileInputStream = new FileInputStream(new File(path));
//3.拼写图片路径
String filename = new SimpleDateFormat("yyyy/MM/dd").format(new Date())
+"/"+ UUID.randomUUID().toString() + path.substring(path.lastIndexOf("."));
// Endpoint以华南(广州)为例,其它Region请按实际情况填写。
String endpoint = "oss-cn-guangzhou.aliyuncs.com";
//阿里云的id和secret
String accessKeyId = "id";
String accessKeySecret = "key";
// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId,accessKeySecret);
//填写Bucket名称和Object完整路径,Object完整路径中不能包含Bucket名称。
ossClient.putObject("tanhua0001",filename,fileInputStream);
//关闭OSSClient
ossClient.shutdown();
//前面的字符串在oss图片查看里面找到
String url = "https://tanhua0001.oss-cn-guangzhou.aliyuncs.com/" + filename;
System.out.println(url);
}
}
把案例写入自动配置里面
- 创建OssProperties —-autoconfig模块
1
2
3
4
5
6
7
8
9
10
11
12
13
14package com.tanhua.autoconfig.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
public class OssProperties {
private String accessKey;
private String secret;
private String bucketName;
private String url; //域名
private String endpoint;
} - 创建OssTemplate模板对象 —-autoconfig模块
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
41package com.tanhua.autoconfig.template;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.tanhua.autoconfig.properties.OssProperties;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.UUID;
public class OssTemplate {
private OssProperties properties;
public OssTemplate(OssProperties properties){
this.properties = properties;
}
/**
* 文件上传
* 1.文件名称
* 2.输入流
*/
public String upload(String filename, InputStream is){
//拼写图片路径
filename = new SimpleDateFormat("yyyy/MM/dd").format(new Date())
+"/"+ UUID.randomUUID().toString() + filename.substring(filename.lastIndexOf("."));
// Endpoint以华南(广州)为例,其它Region请按实际情况填写。
String endpoint = properties.getEndpoint();
//阿里云的id和secret
String accessKeyId = properties.getAccessKey();
String accessKeySecret = properties.getSecret();
// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId,accessKeySecret);
//填写Bucket名称和Object完整路径,Object完整路径中不能包含Bucket名称。
ossClient.putObject(properties.getBucketName(),filename,is);
//关闭OSSClient
ossClient.shutdown();
//前面的字符串在oss图片查看里面找到
String url = properties.getUrl()+ "/" + filename;
return url;
}
} - TanhuaAutoConfiguration注入容器 —-autoconfig模块
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18package com.tanhua.autoconfig;
public class TanhuaAutoConfiguration {
public SmsTemplate smsTemplate(Smsproperties properties){
return new SmsTemplate(properties);
}
public OssTemplate ossTemplate(OssProperties properties){
return new OssTemplate(properties);
}
} - 修改消费者的yaml文件 —-app模块
1
2
3
4
5
6
7tanhua:
oss:
accessKey: Key
secret: secret
endpoint: oss-cn-guangzhou.aliyuncs.com
bucketName: tanhua0001
url: https://tanhua0001.oss-cn-guangzhou.aliyuncs.com - OssTest单元测试 —-app模块
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class OssTest {
private OssTemplate ossTemplate;
public void testOssremplateUpload() throws FileNotFoundException {
String path = "C:\\Users\\19849\\Desktop\\新建文件夹\\default_cover_2.webp";
FileInputStream fileInputStream = new FileInputStream(new File(path));
String urlimage = ossTemplate.upload(path, fileInputStream);
System.out.println(urlimage);
}如果测试失败,可能是由于服务提供者没有启动
1.4.3人脸识别技术
- 人脸识别(Face Recognition)基于图像或视频中的人脸检测、分析和比对技术,提供对您已获授权前提下的私有数据的人脸检测与属性分析、人脸对比、人脸搜索、活体检测等能力。灵活应用于金融、泛安防、零售等行业场景,满足身份核验、人脸考勤、闸机通行等业务需求
- 官方地址:https://ai.baidu.com/tech/face/
- 入门案例
- 创建人脸识别应用
- 单元测试
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
37package cn.itheima.test;
import com.baidu.aip.face.AipFace;
import org.json.JSONObject;
import java.util.HashMap;
public class FaceTest {
//设置APPID/AK/SK
public static final String APP_ID = "APPID";
public static final String API_KEY = "zAqvCgwiNko8l8bRX8bwacr0";
public static final String SECRET_KEY = "LaUFQsMuBPHoslZ4bMYVnmUOUwz0G2zh";
public static void main(String[] args) {
// 初始化一个AipFace
AipFace client = new AipFace(APP_ID, API_KEY, SECRET_KEY);
// 可选:设置网络连接参数
client.setConnectionTimeoutInMillis(2000);
client.setSocketTimeoutInMillis(60000);
// 调用接口
String image = "图片地址";
String imageType = "URL";
// 传入可选参数调用接口
HashMap<String, String> options = new HashMap<String, String>();
options.put("face_field", "age");
options.put("max_face_num", "2");
options.put("face_type", "LIVE");
options.put("liveness_control", "LOW");
// 人脸检测
JSONObject res = client.detect(image, imageType, options);
System.out.println(res.toString(2));
Object error_code = res.get("error_Code");
System.out.println(error_code);
}
} - 结果
1
2
3
4
5
6
7
8{
"result": null,
"log_id": 78997790,
"error_msg": "image download fail",
"cached": 0,
"error_code": 222204,
"timestamp": 1716886348
}第一次可以查询出来
后面不知道为什么出现image download fail错误
把案例写入自动配置里面
- AipFaceProperties
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24package com.tanhua.autoconfig.properties;
import com.baidu.aip.face.AipFace;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
public class AipFaceProperties {
private String appId;
private String apiKey;
private String secretKey;
//AipFace建议单例使用,避免重复获取access_token
public AipFace aipFace() {
AipFace client = new AipFace(appId, apiKey, secretKey);
// 可选:设置网络连接参数
client.setConnectionTimeoutInMillis(2000);
client.setSocketTimeoutInMillis(60000);
return client;
}
} - AipFaceTemplate
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
36package com.tanhua.autoconfig.template;
import com.baidu.aip.face.AipFace;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.HashMap;
public class AipFaceTemplate {
private AipFace client;
/**
* 检测图片中是否包含人脸
* true:包含
* false:不包含
*/
public boolean detect(String imageUrl) {
// 调用接口
String imageType = "URL";
HashMap<String, String> options = new HashMap<String, String>();
options.put("face_field", "age");
options.put("max_face_num", "2");
options.put("face_type", "LIVE");
options.put("liveness_control", "LOW");
// 人脸检测
JSONObject res = client.detect(imageUrl, imageType, options);
System.out.println(res.toString(2));
Integer error_code = (Integer) res.get("error_code");
return error_code == 0;
}
} - TanhuaAutoConfiguration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class TanhuaAutoConfiguration {
public SmsTemplate smsTemplate(Smsproperties properties){
return new SmsTemplate(properties);
}
public OssTemplate ossTemplate(OssProperties properties){
return new OssTemplate(properties);
}
public AipFaceTemplate aipFaceTemplate(){
return new AipFaceTemplate();
}
}注意:一定要写@EnableConfigurationProperties里面的类,不然会报错
- 消费者app里面的yaml
1
2
3
4
5tanhua:
aip:
appId: id
apiKey: zAqvCgwiNko8l8bRX8bwacr0
secretKey: LaUFQsMuBPHoslZ4bMYVnmUOUwz0G2zh - 单元测试
1
2
3
4
5
6
7
8
9
10
11
12
public class FaceTest {
private AipFaceTemplate template;
public void detectFace() {
String image = "https://tanhua0001.oss-cn-guangzhou.aliyuncs.com/2024/05/28/54b00c19-2b06-433c-b219-ef66b65959a4.webp";
boolean detect = template.detect(image);
}由于前面出现image download fail错误,导致现在test代码也无法跑通
2.完善用户信息
2.1保存用户信息
2.1.1 需求分析
- tb_user_info:用户资料表
- 用户表和用户信息表是一对一的关系,两者采用主键关联的形式配置
- 主键关联:用户表主键和用户资料表主键要保持一致(如:用户表id=1,此用户的资料表id=1)
2.1.2 接口定义
1 | 接口路径:/user/loginReginfo |
2.1.3 接口实现
- 配置dubbo提供者环境
- 创建实体类UserInfo —-domain模块
1
2
3
4
5
6
7
8
9
10
11
12
13
14package com.tanhua.dubbo.domain;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
public abstract class BasePojo implements Serializable {
//自动填充
private Date created;
private Date updated;
} - 创建UserInfoMapper接口 —-db模块
1
2public interface UserInfoMapper extends BaseMapper<UserInfo> {
} - 创建UserInfoApi接口 —-interface模块
1
2
3
4
5public interface UserInfoApi {
public void sava(UserInfo userInfo);
public void update(UserInfo userInfo);
} - 实现接口UserInfoImpl —-db模块
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class UserInfoImpl implements UserInfoApi {
private UserInfoMapper userInfoMapper;
public void sava(UserInfo userInfo) {
userInfoMapper.insert(userInfo);
}
public void update(UserInfo userInfo) {
userInfoMapper.updateById(userInfo);
}
}
- 创建实体类UserInfo —-domain模块
- 在消费者中创建UserInfoController —-api模块
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
public class UserInfoController {
private UserInfoService userInfoService;
/**
* 保存用户信息
* UserInfo
* 请求头携带token
*/
public ResponseEntity loginReginfo( UserInfo userInfo,
{ String token)
//1.判断token是否合法
boolean verifyToken = JwtUtils.verifyToken(token);
if (!verifyToken){
return ResponseEntity.status(401).body(null);
}
//2.向userinfo中设置id
Claims claims = JwtUtils.getClaims(token);
Integer id = (Integer) claims.get("id");
userInfo.setId(Long.valueOf(id));
//3.调用service
userInfoService.save(userInfo);
return ResponseEntity.ok(null);
}
} - 在消费者中创建UserInfoService —-api模块
1
2
3
4
5
6
7
8
9
10
11
public class UserInfoService {
private UserInfoApi userInfoApi;
public void save(UserInfo userInfo){
userInfoApi.sava(userInfo);
}
}2.1.4测试结果
2.2上传用户头像
2.2.1流程分析
- 用户点击上传头像,把图片上传到阿里云oss,并返回url存入avatar字段里面
2.2.2接口定义
1 | 接口路径:/user/loginReginfo/head |
- 文件上传采用POST传递,mvc中经过文件解析器转化为MultipartFile对象处理
- 后续请求中,请求headr中都有Authorization参数。
2.2.3接口实现
- UserInfoController —-app模块
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16//上传用户头像
public ResponseEntity head(MultipartFile headPhoto,
String token)throws IOException {
//1.判断token是否合法
boolean verifyToken = JwtUtils.verifyToken(token);
if (!verifyToken){
return ResponseEntity.status(401).body(null);
}
//2.向userinfo中设置id
Claims claims = JwtUtils.getClaims(token);
Integer id = (Integer) claims.get("id");
//3.调用service
userInfoService.updateHead(headPhoto,id);
return ResponseEntity.ok(null);
} - UserInfoService —-app模块
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17//更新用户头像
public void updateHead(MultipartFile headPhoto, Integer id) throws IOException {
//1、将图片上传到阿里云oss
String imageUrl = ossTemplate.upload(headPhoto.getOriginalFilename(), headPhoto.getInputStream());
//2、调用百度云判断是否包含人脸
boolean detect = aipFaceTemplate.detect(imageUrl);
//2.1 如果不包含人脸,抛出异常
if(!detect) {
throw new RuntimeException();
}else{
//2.2 包含人脸,调用API更新
UserInfo userInfo = new UserInfo();
userInfo.setId(Long.valueOf(id));
userInfo.setAvatar(imageUrl);
userInfoApi.update(userInfo);
}
}
2.2.4测试结果
- 由于模拟器没有摄像头权限,所以无法测试
- 使用postman测试,由于前面的image download fail错误,导致测试失败,但是oss里面可以看到图片
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Lemon的博客!