5.发送短信功能
短信微服务已经准备好,我们就可以继续编写用户中心接口了。
5.1.接口说明
这里的业务逻辑是这样的:
- 1)我们接收页面发送来的手机号码
- 2)生成一个随机验证码
- 3)将验证码保存在服务端
- 4)发送短信,将验证码发送到用户手机
那么问题来了:验证码保存在哪里呢?
验证码有一定有效期,一般是5分钟,我们可以利用Redis的过期机制来保存。
5.2.Redis
5.2.2.Spring Data Redis
官网:http://projects.spring.io/spring-data-redis/
Spring Data Redis,是Spring Data 家族的一部分。 对Jedis客户端进行了封装,与spring进行了整合。可以非常方便的来实现redis的配置和操作。
5.2.3.RedisTemplate基本操作
Spring Data Redis 提供了一个工具类:RedisTemplate。里面封装了对于Redis的五种数据结构的各种操作,包括:
- redisTemplate.opsForValue() :操作字符串
- redisTemplate.opsForHash() :操作hash
- redisTemplate.opsForList():操作list
- redisTemplate.opsForSet():操作set
- redisTemplate.opsForZSet():操作zset
其它一些通用命令,如expire,可以通过redisTemplate.xx()来直接调用
5种结构:
- String:等同于java中的,
Map<String,String>
- list:等同于java中的
Map<String,List<String>>
- set:等同于java中的
Map<String,Set<String>>
- sort_set:可排序的set
- hash:等同于java中的:`Map<String,Map<String,String>>
5.2.4.StringRedisTemplate
RedisTemplate在创建时,可以指定其泛型类型:
- K:代表key 的数据类型
- V: 代表value的数据类型
注意:这里的类型不是Redis中存储的数据类型,而是Java中的数据类型,RedisTemplate会自动将Java类型转为Redis支持的数据类型:字符串、字节、二二进制等等。
不过RedisTemplate默认会采用JDK自带的序列化(Serialize)来对对象进行转换。生成的数据十分庞大,因此一般我们都会指定key和value为String类型,这样就由我们自己把对象序列化为json字符串来存储即可。
因为大部分情况下,我们都会使用key和value都为String的RedisTemplate,因此Spring就默认提供了这样一个实现:
5.2.5.测试
我们在项目中编写一个测试案例:
首先在项目中引入Redis启动器:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
- 1
- 2
- 3
- 4
然后在配置文件中指定Redis地址:
spring:
redis:
host: 192.168.56.101
- 1
- 2
- 3
然后就可以直接注入StringRedisTemplate
对象了:
@RunWith(SpringRunner.class) @SpringBootTest(classes = LyUserService.class) public class RedisTest {
<span class="token annotation punctuation">@Autowired</span> <span class="token keyword">private</span> StringRedisTemplate redisTemplate<span class="token punctuation">;</span> <span class="token annotation punctuation">@Test</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">testRedis</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// 存储数据</span> <span class="token keyword">this</span><span class="token punctuation">.</span>redisTemplate<span class="token punctuation">.</span><span class="token function">opsForValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token string">"key1"</span><span class="token punctuation">,</span> <span class="token string">"value1"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 获取数据</span> String val <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>redisTemplate<span class="token punctuation">.</span><span class="token function">opsForValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">"key1"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"val = "</span> <span class="token operator">+</span> val<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token annotation punctuation">@Test</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">testRedis2</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// 存储数据,并指定剩余生命时间,5小时</span> <span class="token keyword">this</span><span class="token punctuation">.</span>redisTemplate<span class="token punctuation">.</span><span class="token function">opsForValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token string">"key2"</span><span class="token punctuation">,</span> <span class="token string">"value2"</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">,</span> TimeUnit<span class="token punctuation">.</span>HOURS<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token annotation punctuation">@Test</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">testHash</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span> BoundHashOperations<span class="token generics function"><span class="token punctuation"><</span>String<span class="token punctuation">,</span> Object<span class="token punctuation">,</span> Object<span class="token punctuation">></span></span> hashOps <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>redisTemplate<span class="token punctuation">.</span><span class="token function">boundHashOps</span><span class="token punctuation">(</span><span class="token string">"user"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 操作hash数据</span> hashOps<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">"name"</span><span class="token punctuation">,</span> <span class="token string">"jack"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> hashOps<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">"age"</span><span class="token punctuation">,</span> <span class="token string">"21"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 获取单个数据</span> Object name <span class="token operator">=</span> hashOps<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">"name"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"name = "</span> <span class="token operator">+</span> name<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 获取所有数据</span> Map<span class="token generics function"><span class="token punctuation"><</span>Object<span class="token punctuation">,</span> Object<span class="token punctuation">></span></span> map <span class="token operator">=</span> hashOps<span class="token punctuation">.</span><span class="token function">entries</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">for</span> <span class="token punctuation">(</span>Map<span class="token punctuation">.</span>Entry<span class="token generics function"><span class="token punctuation"><</span>Object<span class="token punctuation">,</span> Object<span class="token punctuation">></span></span> me <span class="token operator">:</span> map<span class="token punctuation">.</span><span class="token function">entrySet</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>me<span class="token punctuation">.</span><span class="token function">getKey</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">" : "</span> <span class="token operator">+</span> me<span class="token punctuation">.</span><span class="token function">getValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span>
}
- 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
5.3.controller
/**
* 发送手机验证码
* @param phone
* @return
*/
@PostMapping("code")
public ResponseEntity<Void> sendVerifyCode(String phone) {
Boolean boo = this.userService.sendVerifyCode(phone);
if (boo == null || !boo) {
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
return new ResponseEntity<>(HttpStatus.CREATED);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
5.4.service
这里的逻辑会稍微复杂:
- 生成随机验证码
- 将验证码保存到Redis中,用来在注册的时候验证
- 发送验证码到
ly-sms-service
服务,发送短信
因此,我们需要引入Redis和AMQP:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
添加RabbitMQ和Redis配置:
spring:
redis:
host: 192.168.56.101
rabbitmq:
host: 192.168.56.101
username: leyou
password: leyou
virtual-host: /leyou
template:
retry:
enabled: true
initial-interval: 10000ms
max-interval: 210000ms
multiplier: 2
publisher-confirms: true
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
另外还要用到工具类,生成6位随机码,这个我们封装到了ly-common
中,因此需要引入依赖:
<dependency>
<groupId>com.leyou.common</groupId>
<artifactId>ly-common</artifactId>
<version>${leyou.latest.version}</version>
</dependency>
- 1
- 2
- 3
- 4
- 5
生成随机码的工具:
/**
* 生成指定位数的随机数字
* @param len 随机数的位数
* @return 生成的随机数
*/
public static String generateCode(int len){
len = Math.min(len, 8);
int min = Double.valueOf(Math.pow(10, len - 1)).intValue();
int num = new Random().nextInt(
Double.valueOf(Math.pow(10, len + 1)).intValue() - 1) + min;
return String.valueOf(num).substring(0,len);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
Service代码:
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private AmqpTemplate amqpTemplate;
static final String KEY_PREFIX = “user:code☎️”;
static final Logger logger = LoggerFactory.getLogger(UserService.class);
public Boolean sendVerifyCode(String phone) {
// 生成验证码
String code = NumberUtils.generateCode(6);
try {
// 发送短信
Map<String, String> msg = new HashMap<>();
msg.put(“phone”, phone);
msg.put(“code”, code);
this.amqpTemplate.convertAndSend(“ly.sms.exchange”, “sms.verify.code”, msg);
// 将code存入redis
this.redisTemplate.opsForValue().set(KEY_PREFIX + phone, code, 5, TimeUnit.MINUTES);
return true;
} catch (Exception e) {
logger.error(“发送短信失败。phone:{}, code:{}”, phone, code);
return false;
}
}
- 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
注意:要设置短信验证码在Redis的缓存时间为5分钟
5.5.测试
通过RestClient发送请求试试:
查看Redis中的数据:
查看短信:
6.注册功能
6.1.接口说明
6.2.controller
/**
* 注册
* @param user
* @param code
* @return
*/
@PostMapping("register")
public ResponseEntity<Void> register(User user, @RequestParam("code") String code) {
Boolean boo = this.userService.register(user, code);
if (boo == null || !boo) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
}
return new ResponseEntity<>(HttpStatus.CREATED);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
6.3.service
基本逻辑:
- 1)校验短信验证码
- 2)生成盐
- 3)对密码加密
- 4)写入数据库
- 5)删除Redis中的验证码
public Boolean register(User user, String code) { String key = KEY_PREFIX + user.getPhone(); // 从redis取出验证码 String codeCache = this.redisTemplate.opsForValue().get(key); // 检查验证码是否正确 if (!code.equals(codeCache)) { // 不正确,返回 return false; } user.setId(null); user.setCreated(new Date()); // 生成盐 String salt = CodecUtils.generateSalt(); user.setSalt(salt); // 对密码进行加密 user.setPassword(CodecUtils.md5Hex(user.getPassword(), salt)); // 写入数据库 boolean boo = this.userMapper.insertSelective(user) == 1;
<span class="token comment">// 如果注册成功,删除redis中的code</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>boo<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>redisTemplate<span class="token punctuation">.</span><span class="token function">delete</span><span class="token punctuation">(</span>key<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span> logger<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token string">"删除缓存验证码失败,code:{}"</span><span class="token punctuation">,</span> code<span class="token punctuation">,</span> e<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> boo<span class="token punctuation">;</span>
}
- 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
6.4.测试
我们通过在注册页面测试:
查看数据库:
6.5.服务端数据校验
刚才虽然实现了注册,但是服务端并没有进行数据校验,而前端的校验是很容易被有心人绕过的。所以我们必须在后台添加数据校验功能:
我们这里会使用Hibernate-Validator框架完成数据校验:
而SpringBoot的web启动器中已经集成了相关依赖:
6.5.1.什么是Hibernate Validator
Hibernate Validator是Hibernate提供的一个开源框架,使用注解方式非常方便的实现服务端的数据校验。
官网:http://hibernate.org/validator/
hibernate Validator 是 Bean Validation 的参考实现 。
Hibernate Validator 提供了 JSR 303 规范中所有内置 constraint(约束) 的实现,除此之外还有一些附加的 constraint。
在日常开发中,Hibernate Validator经常用来验证bean的字段,基于注解,方便快捷高效。
6.5.2.Bean校验的注解
常用注解如下:
Constraint | 详细信息 |
---|---|
@Valid | 被注释的元素是一个对象,需要检查此对象的所有字段值 |
@Null | 被注释的元素必须为 null |
@NotNull | 被注释的元素必须不为 null |
@AssertTrue | 被注释的元素必须为 true |
@AssertFalse | 被注释的元素必须为 false |
@Min(value) | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 |
@Max(value) | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 |
@DecimalMin(value) | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 |
@DecimalMax(value) | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 |
@Size(max, min) | 被注释的元素的大小必须在指定的范围内 |
@Digits (integer, fraction) | 被注释的元素必须是一个数字,其值必须在可接受的范围内 |
@Past | 被注释的元素必须是一个过去的日期 |
@Future | 被注释的元素必须是一个将来的日期 |
@Pattern(value) | 被注释的元素必须符合指定的正则表达式 |
被注释的元素必须是电子邮箱地址 | |
@Length | 被注释的字符串的大小必须在指定的范围内 |
@NotEmpty | 被注释的字符串的必须非空 |
@Range | 被注释的元素必须在合适的范围内 |
@NotBlank | 被注释的字符串的必须非空 |
@URL(protocol=,host=, port=,regexp=, flags=) | 被注释的字符串必须是一个有效的url |
@CreditCardNumber | 被注释的字符串必须通过Luhn校验算法,银行卡,信用卡等号码一般都用Luhn计算合法性 |
6.5.3.给User添加校验
我们在ly-user-interface
中添加Hibernate-Validator依赖:
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
- 1
- 2
- 3
- 4
我们在User对象的部分属性上添加注解:
@Table(name = "tb_user") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Length(min = 4, max = 30, message = "用户名只能在4~30位之间") private String username;// 用户名
<span class="token annotation punctuation">@JsonIgnore</span> <span class="token annotation punctuation">@Length</span><span class="token punctuation">(</span>min <span class="token operator">=</span> <span class="token number">4</span><span class="token punctuation">,</span> max <span class="token operator">=</span> <span class="token number">30</span><span class="token punctuation">,</span> message <span class="token operator">=</span> <span class="token string">"用户名只能在4~30位之间"</span><span class="token punctuation">)</span> <span class="token keyword">private</span> String password<span class="token punctuation">;</span><span class="token comment">// 密码</span> <span class="token annotation punctuation">@Pattern</span><span class="token punctuation">(</span>regexp <span class="token operator">=</span> <span class="token string">"^1[35678]\\d{9}$"</span><span class="token punctuation">,</span> message <span class="token operator">=</span> <span class="token string">"手机号格式不正确"</span><span class="token punctuation">)</span> <span class="token keyword">private</span> String phone<span class="token punctuation">;</span><span class="token comment">// 电话</span> <span class="token keyword">private</span> Date created<span class="token punctuation">;</span><span class="token comment">// 创建时间</span> <span class="token annotation punctuation">@JsonIgnore</span> <span class="token keyword">private</span> String salt<span class="token punctuation">;</span><span class="token comment">// 密码的盐值</span>
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
6.5.4.在controller上进行控制
在controller中只需要给User添加 @Valid注解即可。
6.5.5.测试
我们故意填错:
然后SpringMVC会自动返回错误信息:
7.根据用户名和密码查询用户
7.1.接口说明
功能说明
查询功能,根据参数中的用户名和密码查询指定用户
接口路径
GET /query
- 1
参数说明:
form表单格式
参数 | 说明 | 是否必须 | 数据类型 | 默认值 |
---|---|---|---|---|
username | 用户名,格式为4~30位字母、数字、下划线 | 是 | String | 无 |
password | 用户密码,格式为4~30位字母、数字、下划线 | 是 | String | 无 |
返回结果:
用户的json格式数据
{
"id": 6572312,
"username":"test",
"phone":"13688886666",
"created": 1342432424
}
- 1
- 2
- 3
- 4
- 5
- 6
状态码:
- 200:注册成功
- 400:用户名或密码错误
- 500:服务器内部异常,注册失败
7.2.controller
/**
* 根据用户名和密码查询用户
* @param username
* @param password
* @return
*/
@GetMapping("query")
public ResponseEntity<User> queryUser(
@RequestParam("username") String username,
@RequestParam("password") String password
) {
User user = this.userService.queryUser(username, password);
if (user == null) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
}
return ResponseEntity.ok(user);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
7.3.service
public User queryUser(String username, String password) {
// 查询
User record = new User();
record.setUsername(username);
User user = this.userMapper.selectOne(record);
// 校验用户名
if (user == null) {
return null;
}
// 校验密码
if (!user.getPassword().equals(CodecUtils.md5Hex(password, user.getSalt()))) {
return null;
}
// 用户名密码都正确
return user;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
要注意,查询时也要对密码进行加密后判断是否一致。
7.4.测试
</div>
<link href="https://csdnimg.cn/release/phoenix/mdeditor/markdown_views-60ecaf1f42.css" rel="stylesheet">
<div class="more-toolbox">
<div class="left-toolbox">
<ul class="toolbox-list">
<li class="tool-item tool-active is-like "><a href="javascript:;"><svg class="icon" aria-hidden="true">
<use xlink:href="#csdnc-thumbsup"></use>
</svg><span class="name">点赞</span>
<span class="count"></span>
</a></li>
<li class="tool-item tool-active is-collection "><a href="javascript:;" data-report-click="{"mod":"popu_824"}"><svg class="icon" aria-hidden="true">
<use xlink:href="#icon-csdnc-Collection-G"></use>
</svg><span class="name">收藏</span></a></li>
<li class="tool-item tool-active is-share"><a href="javascript:;" data-report-click="{"mod":"1582594662_002"}"><svg class="icon" aria-hidden="true">
<use xlink:href="#icon-csdnc-fenxiang"></use>
</svg>分享</a></li>
<!--打赏开始-->
<!--打赏结束-->
<li class="tool-item tool-more">
<a>
<svg t="1575545411852" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5717" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M179.176 499.222m-113.245 0a113.245 113.245 0 1 0 226.49 0 113.245 113.245 0 1 0-226.49 0Z" p-id="5718"></path><path d="M509.684 499.222m-113.245 0a113.245 113.245 0 1 0 226.49 0 113.245 113.245 0 1 0-226.49 0Z" p-id="5719"></path><path d="M846.175 499.222m-113.245 0a113.245 113.245 0 1 0 226.49 0 113.245 113.245 0 1 0-226.49 0Z" p-id="5720"></path></svg>
</a>
<ul class="more-box">
<li class="item"><a class="article-report">文章举报</a></li>
</ul>
</li>
</ul>
</div>
<div class="right-toolbox"><a href="https://blog.csdn.net/id5555/article/details/89877179" target="_blank" class="jump-net-article">
<svg t="1575545252354" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5597" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M243.9 1022.2c-62.3 0-124-23.8-171.5-70.8C26.4 905.5 1.5 845 1.5 779.9s24.9-125.6 70.8-171.5l184-184.1c45.9-45.9 106.4-70.8 171.5-70.8s125.6 24.9 171.5 70.8c18.1 18.1 18.1 47 0 65.1s-47 18.1-65.1 0c-28.3-28.3-65.7-43.5-105.9-43.5s-78.1 15.3-105.9 43.7l-184 184c-58.3 58.3-58.3 153.4 0 212.3 28.3 28.3 65.7 43.5 105.9 43.5s78.1-15.3 105.9-43.5l184-184c18.1-18.1 47-18.1 65.1 0 18.1 18.1 18.1 47 0 65.1l-184 184c-46.9 48-109.1 71.2-171.4 71.2z m523.7-423l184-184c94.5-94.5 94.5-248 0-342.5s-248-94.5-342.5 0l-184 184c-18.1 18.1-18.1 47 0 65.1s47 18.1 65.1 0l184-184c28.3-28.3 65.7-43.5 105.9-43.5s78.1 15.3 105.9 43.5c58.3 58.3 58.3 153.4 0 212.3l-184 184c-58.3 58.3-153.4 58.3-212.3 0-18.1-18.1-47-18.1-65.1 0-18.1 18.1-18.1 47 0 65.1 47 47 109.3 70.8 171.5 70.8s123.9-23.2 171.5-70.8z" p-id="5598"></path></svg>
站内首发文章</a></div>
</div>
<div class="person-messagebox">
<div class="left-message"><a href="https://blog.csdn.net/id5555">
<img src="https://profile.csdnimg.cn/8/6/D/3_id5555" class="avatar_pic" username="id5555">
<img src="https://g.csdnimg.cn/static/user-reg-year/1x/3.png" class="user-years">
</a></div>
<div class="middle-message">
<div class="title"><span class="tit"><a href="https://blog.csdn.net/id5555" data-report-click="{"mod":"popu_379"}" target="_blank">smallmartial</a></span>
</div>
<div class="text"><span>发布了94 篇原创文章</span> · <span>获赞 8</span> · <span>访问量 1万+</span></div>
</div>
<div class="right-message">
<a href="https://im.csdn.net/im/main.html?userName=id5555" target="_blank" class="btn btn-sm btn-red-hollow bt-button personal-letter">私信
</a>
<a class="btn btn-sm bt-button personal-watch" data-report-click="{"mod":"popu_379"}">关注</a>
</div>
</div>
</div>
</article>