一、搭建java后端
1、新建一个springboot项目,初始导入spring-boot-starter-data-redis,spring-boot-starter-data-web,lombok依赖
2、进入阿里巴巴短信运营商购买短信服务,记住AppCode。
往下拉会有短信接口示例
public static void main(String[] args) {
String host = "https://dfsns.market.alicloudapi.com";
String path = "/data/send_sms";
String method = "POST";
String appcode = "你自己的AppCode";
Map<String, String> headers = new HashMap<String, String>();
//最后在header中的格式(中间是英文空格)为Authorization:APPCODE 83359fd73fe94948385f570e3c139105
headers.put("Authorization", "APPCODE " + appcode);
//根据API的要求,定义相对应的Content-Type
headers.put("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
Map<String, String> querys = new HashMap<String, String>();
Map<String, String> bodys = new HashMap<String, String>();
bodys.put("content", "code:1234");
bodys.put("phone_number", "156*****140");
bodys.put("template_id", "TPL_0000");
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();
}
}
打开api-gateway-demo-sign-java/pom.xml at master · aliyun/api-gateway-demo-sign-java · GitHub,将相关依赖导入springBoot中,
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- fastJson-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.79</version>
</dependency>
<!--mp-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
<!--mysql连接-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>
<!-- http的各种状态码-->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.4.15</version>
<scope>compile</scope>
</dependency>
<!-- apacha提供的工具类-->
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
<scope>compile</scope>
</dependency>
<!-- http工具包-->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
api-gateway-demo-sign-java/HttpUtils.java at master · aliyun/api-gateway-demo-sign-java · GitHub将HttpUtils.java放入项目中的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() {
public X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] xcs, String str) {
}
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);
}
}
}
3、将统一返回结果R放入utlis包中
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import org.apache.http.HttpStatus;
import java.util.HashMap;
import java.util.Map;
/**
* 返回数据
*/
public class R extends HashMap<String, Object> {
private static final long serialVersionUID = 1L;
public R setData(Object data){
put("data",data);
return this;
}
public <T> T getData(TypeReference<T> typeReference){
Object data = get("data");
String jsonString = JSON.toJSONString(data);
T t = JSON.parseObject(jsonString, typeReference);
return t;
}
public <T> T getData(String key,TypeReference<T> typeReference){
Object data = get(key);
String jsonString = JSON.toJSONString(data);
T t = JSON.parseObject(jsonString, typeReference);
return t;
}
public R() {
put("code", 0);
put("msg", "success");
}
public static R error() {
return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, "未知异常,请联系管理员");
}
public static R error(String msg) {
return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, msg);
}
public static R error(int code, String msg) {
R r = new R();
r.put("code", code);
r.put("msg", msg);
return r;
}
public static R ok(String msg) {
R r = new R();
r.put("msg", msg);
return r;
}
public static R ok(Map<String, Object> map) {
R r = new R();
r.putAll(map);
return r;
}
public static R ok() {
return new R();
}
public Integer getCode(){
return (Integer) this.get("code");
}
@Override
public R put(String key, Object value) {
super.put(key, value);
return this;
}
}
4、将随机生成验证码工具类ValidateCodeUtils放入utils包中
import java.util.Random;
/**
* 随机生成验证码工具类
* @author superman
*/
public class ValidateCodeUtils {
/**
* 随机生成验证码
* @param length 长度为4位或者6位
* @return
*/
public static Integer generateValidateCode(int length){
Integer code =null;
if(length == 4){
//生成随机数,最大为9999
code = new Random().nextInt(9999);
if(code < 1000){
//保证随机数为4位数字
code = code + 1000;
}
}else if(length == 6){
//生成随机数,最大为999999
code = new Random().nextInt(999999);
if(code < 100000){
//保证随机数为6位数字
code = code + 100000;
}
}else{
throw new RuntimeException("只能生成4位或6位数字验证码");
}
return code;
}
/**
* 随机生成指定长度字符串验证码
* @param length 长度
* @return
*/
public static String generateValidateCode4String(int length){
Random rdm = new Random();
String hash1 = Integer.toHexString(rdm.nextInt());
String capstr = hash1.substring(0, length);
return capstr;
}
}
5、自定义一个缓存key的前缀SMSConstant类放入constant包中
public class SMSConstant {
public static final String SMS_CODE_CACHE_PREFIX="sms:code:";
}
6、在application.yml文件中配置redis的相关配置与端口号
spring:
redis:
password: 123321
host: 127.0.0.1
port: 6379
database: 0
server:
port: 8888
7、定义service接口LoginService
/**
* @author superman
*/
public interface LoginService {
/**
* 像手机发送验证码
* @param phone 手机号
* @param code 验证码
*/
void sendCode(String phone,String code);
}
8、定义该接口的实现类LoginServiceImpl
import com.liulala.service.LoginService;
import com.liulala.utils.HttpUtils;
import org.apache.http.HttpResponse;
import org.apache.http.util.EntityUtils;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
@Service
public class LoginServiceImpl implements LoginService {
private String host ="https://dfsns.market.alicloudapi.com";
private String path ="/data/send_sms";
private String templateId = "TPL_0000";
private String appcode = "第2步中的appcode码";
@Override
public void sendCode(String phone, String code) {
String method = "POST";
Map<String, String> headers = new HashMap<>();
//最后在header中的格式(中间是英文空格)为Authorization:APPCODE 83359fd73fe94948385f570e3c139105
headers.put("Authorization", "APPCODE " + appcode);
//根据API的要求,定义相对应的Content-Type
headers.put("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
Map<String, String> querys = new HashMap<>();
Map<String, String> bodys = new HashMap<>();
bodys.put("content", "code:"+code);
bodys.put("phone_number", phone);
bodys.put("template_id", templateId);
try {
HttpResponse response = HttpUtils.doPost(host, path, method, headers, querys, bodys);
//获取response的body
System.out.println(EntityUtils.toString(response.getEntity()));
} catch (Exception e) {
e.printStackTrace();
}
}
}
9、定义LoginController类
import com.liulala.constant.SMSConstant;
import com.liulala.service.LoginService;
import com.liulala.utils.R;
import com.liulala.utils.ValidateCodeUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.*;
import java.util.concurrent.TimeUnit;
@RestController
public class LoginController {
@Autowired
StringRedisTemplate redisTemplate;
@Autowired
LoginService loginService;
@GetMapping("/sms/sendCode")
public R sendCode(@RequestParam("phone") String phone) {
//1、从redis中获取该手机号存储的验证码
String redisCode = redisTemplate.opsForValue().get(SMSConstant.SMS_CODE_CACHE_PREFIX + phone);
//如果存储过验证码,并且当前次验证码与上一次验证码时间间隔小于60s,则返回
if (StringUtils.isNotEmpty(redisCode)) {
//获取上次验证码发送时的时间
long time = Long.parseLong(redisCode.split("_")[1]);
//如果该两次验证码发送间隔小于60s
if (System.currentTimeMillis() - time < 60 * 1000) {
//60s内不能再刷
return R.error(10002, "短信发送频率过快,请稍后再试");
}
}
//2、如果第一次发送验证码或者两次验证码间隔时间超过60s,则生成一个验证码,将验证码与生成验证码的时间拼为一个新字符串
String code = ValidateCodeUtils.generateValidateCode(4).toString()+"_" + System.currentTimeMillis();
//3、将验证码存储到缓存中,key为设定的前缀+手机号,值为验证码+生成验证码的时间(用于接口防刷),10分钟后过期删除验证码
redisTemplate.opsForValue().set(SMSConstant.SMS_CODE_CACHE_PREFIX + phone, code, 10, TimeUnit.MINUTES);
//4、将验证码发送给手机
loginService.sendCode(phone,code.split("_")[0]);
return R.ok();
}
/**
* 登录
* @param phone
* @param code
* @return
*/
@GetMapping("/login")
public R login(@RequestParam("phone") String phone,@RequestParam("code") String code){
//登录,从redis中获取验证码
String redisCode = redisTemplate.opsForValue().get(SMSConstant.SMS_CODE_CACHE_PREFIX +phone);
if(StringUtils.isNotEmpty(redisCode)){
if(code.equals(redisCode.split("_")[0])){
//匹配成功,删除验证码
redisTemplate.delete(SMSConstant.SMS_CODE_CACHE_PREFIX +phone);
return R.ok();
}
}
return R.error(10003,"验证码错误");
}
}
10、主启动程序排除数据库相关依赖,
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class TestSmsApplication {
public static void main(String[] args) {
SpringApplication.run(TestSmsApplication.class, args);
}
}
后端功能完成,
项目结构为
二:搭建unity前端
1、编写脚本LoginController,将脚本挂在任一物体上,这里挂在主相机上
using System;
using System.Collections;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;
public class LoginController : MonoBehaviour
{
public Text phone;
public Text code;
public Text resultText;
// Update is called once per frame
void Update()
{
}
//验证码登录
public void Login() {
StartCoroutine(CodeUserLogin(phone.text, code.text));
}
//发送短信验证码
public void SendCode() {
StartCoroutine(SendPhoneCode(phone.text));
}
//登录验证短信验证码
IEnumerator CodeUserLogin(string phone, string code)
{
UnityWebRequest unityWebRequest = UnityWebRequest.Get("http://localhost:8888/login?phone=" + phone+"&code="+code); //创建UnityWebRequest对象
yield return unityWebRequest.SendWebRequest(); //等待返回请求的信息
if (unityWebRequest.result == UnityWebRequest.Result.ProtocolError ||
unityWebRequest.result == UnityWebRequest.Result.ConnectionError) //如果其 请求失败,或是 网络错误
{
Debug.Log(unityWebRequest.error); //打印错误原因
}
else //请求成功
{
//如果访问的链接有返回文本结果,比如json文本,则通过text获取
string result = unityWebRequest.downloadHandler.text;
var r = JsonUtility.FromJson<R>(result);
//将后端返回结果显示在UI
print(r.msg);
resultText.text = r.msg;
if (r.code == 0) {
Debug.Log("登录成功");
resultText.text = "登录成功";
}
}
}
//发送短信验证码到手机
IEnumerator SendPhoneCode(string phone)
{
UnityWebRequest unityWebRequest = UnityWebRequest.Get("http://localhost:8888/sms/sendCode?phone="+phone); //创建UnityWebRequest对象
yield return unityWebRequest.SendWebRequest();
if (unityWebRequest.result == UnityWebRequest.Result.ProtocolError ||
unityWebRequest.result == UnityWebRequest.Result.ConnectionError)
{
Debug.Log(unityWebRequest.error); //打印错误原因
}
else //请求成功
{
string result = unityWebRequest.downloadHandler.text;
var r = JsonUtility.FromJson<R>(result);
print(r.msg);
//将后端返回结果显示在UI
resultText.text = r.msg;
if (r.code == 0)
{
Debug.Log("发送验证码成功");
resultText.text = "成功发送验证码";
}
}
}
}
[Serializable]
class R
{
public int code;
public string msg;
public R()
{
}
override
public string ToString()
{
return "code:" + code + ",msg" + msg;
}
}
2、创建一个scene,scene中创建2个inputField,2个button,1个显示结果的resultText,挂载响应的text
3、sendCodeButton绑定单击事件LoginController中的 sendCode方法
loginButton绑定单击事件LoginController中的Login方法
4、启动后台与unity进行测试,
输入手机号,点击发送验证码,手机收到验证码,redis中会存储相应的验证码,过期时间10分钟,验证码输入,验证通过后删除掉该验证码
1分钟内再次点击发送验证码
输入错误验证码
输入正确验证码,redis中保存的验证码会被清除
5、后续需要完善的功能:
1)添加前端与后端的手机号的正则验证,
2)通过手机号去数据库中查询用户信息,若未查到,则创建用户并保存在数据库中
3)将用户信息作为value,生成一个随机token值作为key保存在redis中,
4)将token值返回给前端,
5)untiy前端通过UnityWebRequest的SetRequestHeader方法设置请求头,需要token验证的请求可以将token放在请求头中来访问。
6)后端添加一个拦截器,拦截前端每一次请求的请求头中 的token
7)若token不为空,并且能通过token从redis中查到用户信息,则将用户信息存储在threadlocal中,其他类需要用户信息从threadlocal中获取