在线社交是互联网时代的产物,已成为互联网用户的基础需求之一。移动互联网自2003年起快速发展,促使在线社交逐渐从PC端转移至移动端。移动社交最初以熟人社交为主,以维系熟人关系、共享资源信息的形式存在。随着人们交友需求的延伸,移动社交开始向陌生人社交、兴趣社交等垂直方向发展,形式丰富多样。
探花交友项目定位于 陌生人交友市场。
根据市场现状以及融资事件来看:陌生人社交、内容社群、兴趣社交在2019年仍然保持强劲的动力,占到近70%的比例,它们仍然是资本市场主要关注领域。从增长率来看陌生人社交的增长速度远远大于其他几类,因此我们要从这个方向入手。
从整体年龄段来看:目前目标用户群体主要以30岁以下为主,其中以18-25岁年龄群体为主要受众人群。
用户场景一:
关键词:内向、社交障碍、不主动
大学二年级的陈烨是一位品学兼优且容貌昳丽的小女生,但从小到大的朋友特别少。在聚会时大家都觉得她很高冷,但是陈烨只是不会找时机插不上话,偶尔说上几句也是如细雨飘过。在各类群体社交场合也难以融入人群。
后来,看到室友小白在玩一款陌生人社交软件并引起了她的兴趣,她可以在软件中建立一个内向真实的自己,尝试学会更主动更热情地去了解他人。
但是,玩了一段时间后发现很多陌生人都不愿意与她长聊,或者说聊久了无话可说缺乏话题逐渐变成了好友列表里的一个摆设。
在某乎的某个回答中她看到探花交友App,抱着试一试的心态也尝试着体验了一番,从一开始的每天匹配随心聊天到后来认识到几个有共同爱好的朋友。这同时也让她在社交中慢慢提升自己变得更好。
用户场景二:
关键词:分享、互动、娱乐
陈莹是一位初入职场的新人,喜欢看书、听音乐、创作、拍照….几乎对什么都感兴趣,在毕业后她发现认识新朋友,和新朋友一起出去玩的机会越来越少了。朋友圈里的大家都是二点一线的生活,陈莹喜欢晒生活,说趣闻,发心情。但是,对于朋友圈这个“大杂烩”来说,她不想暴露太多的自我。
在一个偶然的机会,她看到微信公众号有一篇关于社交产品的推文,一向对此嗤之以鼻的她突然来了点兴趣。在用了一段时间后,她发现:她每天可以将自己不愿意分享到朋友圈里的内容,分享到社交产品里。而且发几条,发的内容是什么,她也可以经常将自己所想,所写,所拍都上传到“圈子”里。
对于懂这些东西的人,他们会主动的聚集过来讨论。因此,她也加入到某个兴趣小组,时不时与他们在线上探讨一些问题。陈莹不但找到了属于她自己的社交圈子,同时也找到一个可以随时随地分享点滴的平台。
用户场景三:
关键词:脱单、脱单、脱单
作为一个直男,宋沌堪称直男教学书一般的案例,他的行为类似下图:
曾谈过几次恋爱,都以迅速失败告终。作为一个长相一般,身家一般,谈吐一般的综合表现男来说,他基本把自己定义成街上一抓一大把的类型。但是,作为一个直男的他也是有个异性梦,每天都梦想着有一个女友,所以他也不断在尝试。
他几乎下载了市面上所有的社交产品,摆上了经过“特殊处理”的自拍照,时不时更新自己的动态。但即便如此,宋沌依然没有几个异性聊友,宋沌也反省过自己,主要是自己每次图一时新鲜,聊一段时间就不感兴趣了,而且由于自己比较害羞所以聊天也容易尬聊。
在朋友的介绍下,他下载了探花APP,由于属于陌生人社交,宋沌可以不用有太多的思想压力,经过几天的好友配对,找到了合适的朋友,每天发一些日常生活的消息,也能获得更多的关注,自信心逐渐增长,聊天技巧也有所提升。
根据我们的市场调研以及分析:从产品细分领域以及对应的产品定位来选择,我们选择了社交范围内的兴趣社交App作为竞品分析的案例。
其中,我们发现:市面上的兴趣社交产品还是较多的,例如花田、soul、探探、陌陌等等,最终我们选择了花田、SOUL和陌陌。
**花田:**更偏向打造兴趣匹配,并配合线下活动俩者结合提升产品服务。給每一个热爱青年文化的用户营造出归属感,并促使用户自主的生产内容,形成一个良性的娱乐社交平台。
**SOUL:**更注重用户灵魂(内涵)的产品,一定程度上,SOUL摒弃了传统社交的以颜值优先,内容其次的特点。将自身的个性以及特点先展现出去,然后再以内部算法为匹配手段,通过图文内容进行用户交流。
**陌陌:**陌陌是一款基于地理位置的移动社交工具。使用者可以通过陌陌认识附近的人,免费发送文字消息、语音、照片以及精准的地理位置和身边的人更好的交流;可以使用陌陌创建和加入附近的兴趣小组、留言及附近活动和陌陌吧。
三款产品各具风格,各有特点,但有一点是三款产品都有一个核心观点,就是:弱化肤浅的目的,利用人类自带的自我认识的本能来结识陌生人。总结而言,就是:希望满足用户『探索自我』的娱乐性。
探花交友是一个陌生人的在线交友平台,在该平台中可以搜索附近的人,查看好友动态,平台还会通过大数据计算进行智能推荐,通过智能推荐可以找到更加匹配的好友,这样才能增进用户对产品的喜爱度。探花平台还提供了在线即时通讯功能,可以实时的与好友进行沟通,让沟通随时随地的进行。
前端:
后端:
前端:
后端:
项目基于前后端分离的架构进行开发,总体包括前端(客户端)和后端(服务端),通常由多人协作开发
提供了已经安装好服务的centos7镜像,直接导入到VMware中即可,root用户的密码为root123。
客户端由前端团队开发,使用APK进行对接,因此需安装安卓模拟器进行测试
–推荐使用网易模拟器,兼容性好
–下载地址:https://mumu.163.com/
Postman是一款Chrome插件,为用户提供功能强大的 Web API & HTTP 请求调试,被500万开发者和超100,000家公司用于每月访问1.3亿个API
整体项目使用Maven架构搭建
采用聚合工程形式管理模块
为了便于调用,Dubbo需要拆分为接口模块和服务模块
父工程 | 工程名称 | 说明 |
---|---|---|
tanhua | tanhua-autoconfig | 自动装配组件(短信等) |
tanhua | tanhua-model | 实体类模块 |
tanhua | tanhua-dubbo | Dubbo子模块(可以理解为文件夹,管理Dubbo模块) |
tanhua | tanhua-commons | 工具类 |
tanhua | tanhua-app-server | 与手机端交互的入口模块 |
tanhua-dubbo | tanhua-dubbo-interface | Dubbo接口模块 |
tanhua-dubbo | tanhua-dubbo-db | Dubbo服务模块(数据库部分) |
tanhua-dubbo | tanhua-dubbo-mongo | Dubbo服务模块(MongoDB部分) |
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.3.9.RELEASEversion>
parent>
<properties>
<maven.compiler.source>8maven.compiler.source>
<maven.compiler.target>8maven.compiler.target>
<mysql.version>5.1.47mysql.version>
<jackson.version>2.11.0jackson.version>
<druid.version>1.0.9druid.version>
<servlet-api.version>2.5servlet-api.version>
<jsp-api.version>2.0jsp-api.version>
<joda-time.version>2.5joda-time.version>
<commons-lang3.version>3.3.2commons-lang3.version>
<commons-io.version>1.3.2commons-io.version>
<mybatis.version>3.5.6mybatis.version>
<mybatis.mybatis-plus>3.4.1mybatis.mybatis-plus>
<lombok.version>1.18.8lombok.version>
<mongo.version>4.0.5mongo.version>
<spring-cloud.version>Hoxton.SR10spring-cloud.version>
<spring-cloud-alibaba.version>2.2.5.RELEASEspring-cloud-alibaba.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
<dependency>
<groupId>cn.hutoolgroupId>
<artifactId>hutool-allartifactId>
<version>5.4.3version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.8version>
dependency>
dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.nettygroupId>
<artifactId>netty-bomartifactId>
<version>4.1.59.Finalversion>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>io.projectreactorgroupId>
<artifactId>reactor-bomartifactId>
<version>2020.0.4version>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>io.projectreactor.nettygroupId>
<artifactId>reactor-nettyartifactId>
<version>0.9.8.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>${spring-cloud.version}version>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-dependenciesartifactId>
<version>${spring-cloud-alibaba.version}version>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>joda-timegroupId>
<artifactId>joda-timeartifactId>
<version>${joda-time.version}version>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plusartifactId>
<version>${mybatis.mybatis-plus}version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>${mysql.version}version>
dependency>
<dependency>
<groupId>org.mongodbgroupId>
<artifactId>mongodb-driver-syncartifactId>
<version>${mongodb.version}version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
<version>${lombok.version}version>
dependency>
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-lang3artifactId>
<version>${commons-lang3.version}version>
dependency>
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-lang3artifactId>
<version>3.7version>
dependency>
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-databindartifactId>
<version>${jackson.version}version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>${druid.version}version>
dependency>
<dependency>
<groupId>commons-codecgroupId>
<artifactId>commons-codecartifactId>
<version>1.11version>
dependency>
dependencies>
dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<version>3.2version>
<configuration>
<source>1.8source>
<target>1.8target>
<encoding>UTF-8encoding>
configuration>
plugin>
plugins>
build>
<dependencies>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plusartifactId>
dependency>
dependencies>
<build>
<finalName>tanhua-modelfinalName>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
<version>2.3.9.RELEASEversion>
plugin>
plugins>
build>
<dependencies>
<dependency>
<groupId>com.aliyungroupId>
<artifactId>aliyun-java-sdk-coreartifactId>
<version>4.5.3version>
dependency>
<dependency>
<groupId>com.aliyungroupId>
<artifactId>dysmsapi20170525artifactId>
<version>2.0.1version>
dependency>
<dependency>
<groupId>com.aliyun.ossgroupId>
<artifactId>aliyun-sdk-ossartifactId>
<version>3.10.2version>
dependency>
<dependency>
<groupId>com.aliyungroupId>
<artifactId>facebody20191230artifactId>
<version>1.0.10version>
dependency>
<dependency>
<groupId>com.baidu.aipgroupId>
<artifactId>java-sdkartifactId>
<version>4.8.0version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>com.easemob.imgroupId>
<artifactId>im-sdk-coreartifactId>
<version>0.2.5version>
dependency>
<dependency>
<groupId>com.aliyungroupId>
<artifactId>aliyun-java-sdk-greenartifactId>
<version>3.6.1version>
dependency>
dependencies>
<dependencies>
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-lang3artifactId>
dependency>
<dependency>
<groupId>commons-codecgroupId>
<artifactId>commons-codecartifactId>
dependency>
<dependency>
<groupId>joda-timegroupId>
<artifactId>joda-timeartifactId>
dependency>
<dependency>
<groupId>io.jsonwebtokengroupId>
<artifactId>jjwtartifactId>
<version>0.9.1version>
dependency>
dependencies>
<dependencies>
<dependency>
<groupId>com.heimagroupId>
<artifactId>tanhua-modelartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
dependencies>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plusartifactId>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>${mybatis.mybatis-plus}version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-lang3artifactId>
dependency>
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-databindartifactId>
dependency>
<dependency>
<groupId>commons-codecgroupId>
<artifactId>commons-codecartifactId>
dependency>
<dependency>
<groupId>joda-timegroupId>
<artifactId>joda-timeartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-dubboartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-configartifactId>
dependency>
<dependency>
<groupId>com.heimagroupId>
<artifactId>tanhua-dubbo-interfaceartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
dependencies>
<build>
<finalName>tanhua-dubbo-dbfinalName>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-lang3artifactId>
dependency>
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-databindartifactId>
dependency>
<dependency>
<groupId>commons-codecgroupId>
<artifactId>commons-codecartifactId>
dependency>
<dependency>
<groupId>joda-timegroupId>
<artifactId>joda-timeartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-dubboartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
<dependency>
<groupId>com.heimagroupId>
<artifactId>tanhua-commonsartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>com.heimagroupId>
<artifactId>tanhua-dubbo-interfaceartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
dependencies>
<build>
<finalName>tanhua-dubbo-mongofinalName>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
<version>2.3.9.RELEASEversion>
plugin>
plugins>
build>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-lang3artifactId>
dependency>
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-databindartifactId>
dependency>
<dependency>
<groupId>commons-codecgroupId>
<artifactId>commons-codecartifactId>
dependency>
<dependency>
<groupId>joda-timegroupId>
<artifactId>joda-timeartifactId>
dependency>
<dependency>
<groupId>io.jsonwebtokengroupId>
<artifactId>jjwtartifactId>
<version>0.9.1version>
dependency>
<dependency>
<groupId>com.github.tobatogroupId>
<artifactId>fastdfs-clientartifactId>
<version>1.26.7version>
<exclusions>
<exclusion>
<groupId>ch.qos.logbackgroupId>
<artifactId>logback-classicartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-dubboartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
<dependency>
<groupId>com.heimagroupId>
<artifactId>tanhua-dubbo-interfaceartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>com.heimagroupId>
<artifactId>tanhua-commonsartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>com.heimagroupId>
<artifactId>tanhua-autoconfigartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
dependencies>
<build>
<finalName>tanhua-app-serverfinalName>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
<version>2.3.9.RELEASEversion>
plugin>
plugins>
build>
短信服务(Short Message Service)由阿里云提供短信平台,调用API即可发送验证码、通知类和营销类短信;国内验证短信秒级触达,到达率最高可达99%。
官方网站:https://www.aliyun.com/product/sms?spm=5176.19720258.J_8058803260.611.48192c4abPvXEp
public static void main(String[] args_) throws Exception {
String accessKeyId = "LTAI4GKgob9vZ53k2SZdyAC7";
String accessKeySecret= "LHLBvXmILRoyw0niRSBuXBZewQ30la";
//配置阿里云
Config config = new Config()
// 您的AccessKey ID
.setAccessKeyId(accessKeyId)
// 您的AccessKey Secret
.setAccessKeySecret(accessKeySecret);
// 访问的域名
config.endpoint = "dysmsapi.aliyuncs.com";
com.aliyun.dysmsapi20170525.Client client = new com.aliyun.dysmsapi20170525.Client(config);
SendSmsRequest sendSmsRequest = new SendSmsRequest()
.setPhoneNumbers("")
.setSignName("物流云商")
.setTemplateCode("SMS_205134115")
.setTemplateParam("{\"code\":\"1234\"}");
// 复制代码运行请自行打印 API 的返回值
SendSmsResponse response = client.sendSms(sendSmsRequest);
SendSmsResponseBody body = response.getBody();
}
根据自动装配原则,在tanhua-autoconfig
模块创建/META-INF/spring.factories
文件
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.tanhua.autoconfig.TanhuaAutoConfiguration
tanhua-autoconfig
模块创建自动装配的配置类
package com.tanhua.autoconfig;
import com.tanhua.autoconfig.properties.SmsProperties;
import com.tanhua.autoconfig.templates.SmsTemplate;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
@EnableConfigurationProperties({
SmsProperties.class
})
public class TanhuaAutoConfiguration {
@Bean
public SmsTemplate smsTemplate(SmsProperties smsProperties) {
return new SmsTemplate(smsProperties);
}
}
tanhua-autoconfig
模块创建配置信息类SmsProperties
package com.tanhua.autoconfig.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
@Data
@ConfigurationProperties(prefix = "tanhua.sms")
public class SmsProperties {
private String signName;
private String templateCode;
private String accessKey;
private String secret;
}
tanhua-autoconfig
模块创建模板对象发送信息
public class SmsTemplate {
private SmsProperties properties;
public SmsTemplate(SmsProperties properties) {
this.properties = properties;
}
public void sendSms(String mobile,String code) {
Config config = new Config()
.setAccessKeyId(properties.getAccessKey())
.setAccessKeySecret(properties.getSecret())
.setEndpoint("dysmsapi.aliyuncs.com");
try {
Client client = new Client(config);
SendSmsRequest sendSmsRequest = new SendSmsRequest()
.setPhoneNumbers(mobile)
.setSignName(properties.getSignName())
.setTemplateCode(properties.getTemplateCode())
.setTemplateParam("{\"code\":\"" + code + "\"}");
SendSmsResponse response = client.sendSms(sendSmsRequest);
System.out.println(response.getBody().toString());
System.out.println(response.getBody().message);
} catch (Exception e) {
e.printStackTrace();
}
}
}
tanhua-app-server
端添加引导类com.tanhua.server.AppServerApplication
package com.tanhua.server;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
//启动类
@SpringBootApplication
public class AppServerApplication {
public static void main(String[] args) {
SpringApplication.run(AppServerApplication.class,args);
}
}
tanhua-app-server
工程加入短信配置
tanhua:
sms:
signName: 物流云商
templateCode: SMS_106590012
accessKey: LTAI4GKgob9vZ53k2SZdyAC7
secret: LHLBvXmILRoyw0niRSBuXBZewQ30la
tanhua-app-server
工程编写单元测试类
@RunWith(SpringRunner.class)
@SpringBootTest(classes = AppServerApplication.class)
public class SmsTemplateTest {
//注入
@Autowired
private SmsTemplate smsTemplate;
//测试
@Test
public void testSendSms() {
smsTemplate.sendSms("xxxxxxxxxxx","4567");
}
}
地址:http://192.168.136.160:3000/project/19/interface/api/94
tanhua-app-server
端添加配置文件application.yml
#服务端口
server:
port: 18080
spring:
application:
name: tanhua-app-server
redis: #redis配置
port: 6379
host: 192.168.136.160
cloud: #nacos配置
nacos:
discovery:
server-addr: 192.168.136.160:8848
dubbo: #dubbo配置
registry:
address: spring-cloud://localhost
consumer:
check: false
tanhua:
sms:
signName: 物流云商
templateCode: SMS_106590012
accessKey: LTAI4GKgob9vZ53k2SZdyAC7
secret: LHLBvXmILRoyw0niRSBuXBZewQ30la
tanhua-app-server
工程编写com.tanhua.server.controller.LoginController#login
在LoginController中编写用户登录,发送验证码方法
package com.tanhua.server.controller;
import com.tanhua.server.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
@RequestMapping("/user")
public class LoginController {
@Autowired
private UserService userService;
/**
* 用户登录-发送验证码
*/
@PostMapping("/login")
public ResponseEntity login(@RequestBody Map map) {
//1、获取手机号码
String mobile = (String) map.get("phone");
//2、调用service发送短信
return userService.sendMsg(mobile);
}
}
**tanhua-app-server
**工程编写 com.tanhua.server.service.UserService
UserService中编写生成验证码,并发送短信方法
package com.tanhua.server.service;
import com.tanhua.autoconfig.templates.SmsTemplate;
import com.tanhua.domain.db.User;
import com.tanhua.dubbo.api.UserApi;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import java.time.Duration;
import java.util.Date;
@Service
public class UserService {
@Reference
private UserApi userApi;
@Autowired
private SmsTemplate smsTemplate;
@Autowired
private RedisTemplate<String,String> redisTemplate;
//对手机号码,发送验证码
public ResponseEntity sendMsg(String mobile) {
//1、生成验证码(6位数字)
String code = RandomStringUtils.randomNumeric(6);
//2、调用template发送短信
smsTemplate.sendSms(mobile,code);
//3、存入redis
redisTemplate.opsForValue().set("CHECK_CODE_"+mobile,code, Duration.ofMinutes(5));//验证码失效时间
//4、构建返回值
return ResponseEntity.ok(null);
}
}
JSON Web Token简称JWT,用于对应用程序上用户进行身份验证的标记。使用 JWTS 之后不需要保存用户的 cookie 或其他session数据,同时可保证应用程序的安全。
JWT是经过加密处理与校验处理的字符串,形式为:A.B.C
@Test
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 + 5000)) //设置失效时间
.compact();
System.out.println(token);
}
@Test
public void testParseToken() {
String token = "eyJhbGciOiJIUzUxMiJ9.e1yJtb2JpbGUiOiIxMzgwMDEzODAwMCIsImlkIjoxLCJleHAiOjE2MTgzOTA4NzB9.WhZfFSJNEZJGeZAHqMaVIslvzvkFzQ1FCLCULSaR-yZYprbzgmuxNaSr3oW__zRFkBCnNRGllzaKb0ZJujs1GA";
//解析token
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不合法");
}
}
接口地址:http://192.168.136.160:3000/project/19/interface/api/97
在tanhua-model
模块定义实体类User
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Date;
@Data
@AllArgsConstructor //满参构造方法
@NoArgsConstructor //无参构造方法
public class User implements Serializable {
private Long id;
private String mobile;
private String password;
private Date created;
private Date updated;
}
在tanhua-dubbo-db
项目中定义UserMapper , 如下所示
package com.tanhua.dubbo.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.tanhua.domain.db.User;
public interface UserMapper extends BaseMapper<User> {
}
tanhua-dubbo-interface
模块定义UserApi接口
/**
* 公共接口
*/
public interface UserApi {
//根据手机号码查询用户
User findByMobile(String mobile);
}
在tanhua-dubbo-db
项目中提供
UserApiImpl的实现 , 如下所示
/**
* dubbo服务
*/
@DubboService
public class UserApiImpl implements UserApi {
@Autowired
private UserMapper userMapper;
//根据手机号码查询用户
public User findByMobile(String mobile) {
QueryWrapper<User> qw = new QueryWrapper<>();
qw.eq("mobile",mobile);
return userMapper.selectOne(qw);
}
}
在tanhua-dubbo-db
项目中提供
引导类
@SpringBootApplication
//mapper扫描
@MapperScan("com.tanhua.dubbo.mappers")
public class DubboServerApplication {
public static void main(String[] args) {
SpringApplication.run(DubboServerApplication.class,args);
}
}
在tanhua-dubbo-db
项目中提供
配置文件application.yml
server:
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: root
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策略为自增长
ErrorResult:统一返回的错误信息对象
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ErrorResult {
private String errCode = "999999";
private String errMessage;
public static ErrorResult error() {
return ErrorResult.builder().errCode("999999").errMessage("系统异常稍后再试").build();
}
public static ErrorResult fail() {
return ErrorResult.builder().errCode("000001").errMessage("发送验证码失败").build();
}
public static ErrorResult loginError() {
return ErrorResult.builder().errCode("000002").errMessage("验证码失效").build();
}
public static ErrorResult faceError() {
return ErrorResult.builder().errCode("000003").errMessage("图片非人像,请重新上传!").build();
}
public static ErrorResult mobileError() {
return ErrorResult.builder().errCode("000004").errMessage("手机号码已注册").build();
}
public static ErrorResult contentError() {
return ErrorResult.builder().errCode("000005").errMessage("动态内容为空").build();
}
public static ErrorResult likeError() {
return ErrorResult.builder().errCode("000006").errMessage("用户已点赞").build();
}
public static ErrorResult disLikeError() {
return ErrorResult.builder().errCode("000007").errMessage("用户未点赞").build();
}
public static ErrorResult loveError() {
return ErrorResult.builder().errCode("000008").errMessage("用户已喜欢").build();
}
public static ErrorResult disloveError() {
return ErrorResult.builder().errCode("000009").errMessage("用户未喜欢").build();
}
}
在tanhua-app-server
项目中接收APP请求, 调用Dubbo服务完成业务功能
/**
* 验证码校验登录
*/
@PostMapping("/loginVerification")
public ResponseEntity loginVerification(@RequestBody Map map) {
String mobile = (String) map.get("phone");
String code = (String) map.get("verificationCode");
return userSerivce.login(mobile,code);
}
/**
* 验证登录
* @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;
}
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信息
*/
public 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;
}
}
为了简化实体类中created和updated字段,抽取BasePojo
@Data
public abstract class BasePojo implements Serializable {
@TableField(fill = FieldFill.INSERT) //自动填充
private Date created;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updated;
}
对于created和updated字段,每次操作都需要手动设置。为了解决这个问题,mybatis-plus支持自定义处理器的形式实现保存更新的自动填充
package com.tanhua.dubbo.server.handler;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
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);
}
}
@Override
public void updateFill(MetaObject metaObject) {
//更新数据时,直接更新字段
setFieldValByName("updated", new Date(), metaObject);
}
}