前言
這幾天想學習網路相關的東西,那么就從最簡單的開始吧,但是在網上搜了一下,百度還是谷歌都沒有完整的教程,要不只有移動端代碼,要不只有后端代碼,不想使用別人的api,那就自己做一個,所以我在完成之后想把這些記錄下來,中間的坑遇到了不少,在這做一個簡簡單單的總結,從搭建環境開始一步一步來,代碼比較粗糙,不過基本功能沒問題,本文章用到的框架不會的可以提前學起來了,
環境介紹
移動端:
1. AndroidStudio 4.1.1
2. 編程語言:kotlin
3. 架構:mvp
4. 框架庫:retrofit2+okhttp3(這里沒用rxjava是因為有kotlin)
后端:
1.JDK 8
2.IntelliJ IDEA 2020
3.Tomcat 8
4.MySQL 5.7.29
5.Navicat Premium 15
6.springboot+mybatis
搭建環境
安卓端的環境搭建不會的可以看我的其他文章,這里不再多說,
對于一個沒有接觸過后端的,首先是要學會搭建環境!這里主要是后端的環境搭建:可以參考這篇文章,寫的很詳細,絕對夠用:
手把手教你搭建開發環境之Java開發
知識儲備
既然我們用retrofit和okhttp,那么我們總得知道怎么使用吧,這里推薦個不錯的文章:
你應該知道的HTTP基礎知識
你真的會用Retrofit2嗎?Retrofit2完全教程
后端的不會的可以直接看b站的視頻:
我們本篇文章的主要目的不是學習后端
SpringBoot系列–SSM1: 注冊登錄
后端環境搭建好之后,基本上跟著視頻來一步一步的做出來就沒問題了,如果有問題可以去看原始碼對比一下,不過小關哥的第三個視頻跳的稍微有點快,對于后端沒碰過的我,是真的菜,這里我遇到了幾個坑,下面會給大家提出來避免入坑,最后也會放上完整代碼的鏈接!
Android端
1.添加依賴
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.okhttp3:okhttp:4.9.0'
implementation 'com.squareup.okhttp3:logging-interceptor:4.9.0'
2.布局
activity_login.xml(部分資源查看原始碼)
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/username_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="@+id/username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/login_username"
android:maxLines="1"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/password_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/username_layout">
<EditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/login_password"
android:imeActionLabel="@string/action_sign_in_short"
android:imeOptions="actionUnspecified"
android:inputType="textPassword"
android:maxLines="1"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/email_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/password_layout">
<EditText
android:id="@+id/email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/prompt_email"
android:inputType="textEmailAddress"
android:maxLines="1"/>
</com.google.android.material.textfield.TextInputLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/email_layout"
android:layout_marginTop="16dp"
android:orientation="horizontal">
<Button
android:id="@+id/register"
style="?android:textAppearanceSmall"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/login_register"
android:textStyle="bold"/>
<Button
android:id="@+id/login"
style="?android:textAppearanceSmall"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/login_login"
android:textStyle="bold"/>
</LinearLayout>
</RelativeLayout>
activity_register.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="20dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/login_username"
android:textSize="16sp"
android:textColor="#2b2b2b"/>
<EditText
android:id="@+id/input_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLength="18"
android:maxLines="1"
android:hint="請輸入用戶名"
android:textSize="14sp"
android:inputType="text"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:text="@string/login_password"
android:textSize="16sp"
android:textColor="#2b2b2b"/>
<EditText
android:id="@+id/input_pwd"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text|textPassword"
android:maxLength="18"
android:hint="請輸入密碼"
android:textSize="14sp"
android:maxLines="1"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:text="@string/confirm_pwd"
android:textSize="16sp"
android:textColor="#2b2b2b"/>
<EditText
android:id="@+id/input_confirm_pwd"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text|textPassword"
android:maxLength="18"
android:hint="請再次輸入密碼"
android:textSize="14sp"
android:maxLines="1"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:text="@string/email"
android:textSize="16sp"
android:textColor="#2b2b2b"/>
<EditText
android:id="@+id/input_email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text"
android:maxLength="18"
android:hint="請輸入郵箱"
android:textSize="14sp"
android:maxLines="1"/>
<Button
android:id="@+id/btn_register"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:textSize="18sp"
android:text="@string/register"/>
</LinearLayout>
效果圖:


Entity
data class Accounts(val status: String,
val msg: String,
val result: User):Serializable
data class User(val id: String, val nickname: String,val password:String,val username:String)
這里的物體我們得和后端同步,首先定義User類,用戶id,昵稱,用戶名和密碼
然后再建一個Accounts賬戶類,包括回應狀態碼,回應資訊,回應資料就是我們的User類,
API
class APIService {
/**
* 注冊
*/
interface Register {
@FormUrlEncoded
@POST("register")
fun toRegister(
@Field("username") username: String,
@Field("password") password: String
): Call<Accounts>
}
/**
* 登錄
*/
interface Login {
@FormUrlEncoded
@POST("login")
fun toLogin(
@Field("username") username: String,
@Field("password") password: String
):Call<Accounts>
}
}
這里我們采用post請求方式,回傳的資料就是我們定義的Accounts類,其他請求方式不再介紹,具體的使用方法見上面的推薦文章,剛開始不理解注解后面的引數含義,就想自己搭建一個后端來看看怎么鏈接的,我的 BaseUrl 是:
const val REQUEST_BASE_URL:String = "http://47.111.233.78:8080/api/user/"
這里我沒有使用localhost而是使用的ip地址,因為我的服務器在云端上面,(這里是使用的阿里云服務器,可以自己搭建一個云端服務器來使用,中間有些狗血的坑,比如我用mstsc來連接云端連接不上,得弄什么注冊表權限,還有就是搭建完之后,一定要在云端的網站配置安全組的常用埠8080,總之比較坎坷,挑選自己的服務器,如果只是測驗,可以選個一個月免費,或者是三個月的比較便宜的輕量型服務器,)
好了,我們看一下一個完整的請求連接是什么樣的:
http://localhost:8080/api/user/login?username=wyq&password=321
我的baseurl是http://localhost:8080/api/user/
而我們需要在api中進行一些操作,相當于login是一個請求登錄的操作,然后傳入你的引數用戶名和密碼
http://localhost:8080/api/user/register?username=wyq&password=321
這就是注冊請求操作,這里只是演示一下,以便于理解,post不能使用這種請求操作,get請求才能,
接著retrofit和okhttp會自動拼接這些引數并傳給后端,后端接收到這些引數會拆分,根據你的請求引數是login還是register來進行進一步的資料庫查詢還是插入操作,
retrofitManager.kt
class RetrofitManager {
private val BASE_URL = "http://47.111.233.78:8080/api/user/"
companion object{
fun <T> getService(url:String,service: Class<T>):T{
//根據你傳的service來進行不同的請求
return createRetrofit(url).create(service)
}
private fun createRetrofit(url: String):Retrofit{
val level: HttpLoggingInterceptor.Level = HttpLoggingInterceptor.Level.BODY
val loggingInterceptor = HttpLoggingInterceptor(object :HttpLoggingInterceptor.Logger{
override fun log(message: String) {
Log.i("kotlin", "OkHttp: $message")
}
})
loggingInterceptor.level = level
val okHttpClientBuilder = OkHttpClient.Builder()
//連接超時
okHttpClientBuilder.connectTimeout(30, TimeUnit.SECONDS)
//讀取超時
okHttpClientBuilder.readTimeout(10, TimeUnit.SECONDS)
//添加攔截器
okHttpClientBuilder.addInterceptor(loggingInterceptor)
//創建retrofit物件
return Retrofit.Builder()
.baseUrl(url)
.client(okHttpClientBuilder.build())
//json資料轉換
.addConverterFactory(GsonConverterFactory.create())
.build()
}
}
}
LoginTask.kt
class LoginTask:LoginContract.Task {
private var callBack: LoginContract.Presenter.OnLoginCallBack? = null
override fun login(
username: String?,
password: String?,
onLoginCallBack: LoginContract.Presenter.OnLoginCallBack
) {
callBack = onLoginCallBack
val mLogin = RetrofitManager.getService(Constant.REQUEST_BASE_URL, APIService.Login::class.java)
if (username!!.isNotEmpty() && password!!.isNotEmpty()){
val longCall = mLogin.toLogin(username, password)
longCall.enqueue(object : Callback<Accounts> {
override fun onFailure(call: Call<Accounts>, t: Throwable) {
callBack?.loginFail("登錄失敗")
}
override fun onResponse(call: Call<Accounts>, response: Response<Accounts>) {
var result: Accounts? = response.body()
if (result != null && "0" == result.status){
callBack?.loginSuccess()
}else{
callBack?.loginFail(result!!.msg)
}
}
})
}
}
}
registerTask.kt
class RegisterTask : RegisterContract.Task {
private var callback: RegisterContract.Presenter.OnRegisterCallBack? = null
override fun goRegister(
name: String,
pwd: String,
onRegisterCallBack: RegisterContract.Presenter.OnRegisterCallBack
) {
callback = onRegisterCallBack
val registerService =
RetrofitManager.getService(Constant.REQUEST_BASE_URL, APIService.Register::class.java)
val registerCallBack = registerService.toRegister(name, pwd)
registerCallBack.enqueue(object : Callback<Accounts> {
override fun onFailure(call: Call<Accounts>, t: Throwable) {
callback?.registerFail("注冊失敗")
}
override fun onResponse(call: Call<Accounts>, response: Response<Accounts>) {
var result: Accounts? = response!!.body()
if (result != null && "0" == result.status) {
callback?.registerSuccess(result)
} else {
callback?.registerFail(result!!.msg)
}
}
})
}
}
后端:
這里就放主要代碼:
@Slf4j
@Api(tags = "用戶介面")
@AllArgsConstructor
@RequestMapping(Const.API + "user")
@RestController
public class UserController {
private final UserService userService;
private final UserMapper userMapper;
@ApiOperation("登陸介面,回傳用戶資料")
@PostMapping("login")
public Result<User> login(
@RequestParam String username,
@RequestParam String password) {
return userService.login(username, password);
}
@ApiOperation("注冊用戶介面")
@PostMapping("register")
public Result<User> register(@RequestParam String username,
@RequestParam String password, String nickname) {
return userService.addUser(username, password, nickname);
}
看到postMapping里面是不是和android里面APIService很相像,是的,這就相當于一個約定,我們在android移動端和后端要約定好介面,才能進行資料請求和回應,
@Slf4j
@AllArgsConstructor
@Service
public class UserService {
private final UserMapper userMapper;
public Result<User> login(String username, String password) {
User user = getUserByUsernameAndPassword(username, password);
if (user == null) {
return Result.createByErrorMessage("登陸失敗");
}
HttpKit.getRequest().getSession().setAttribute("user", user);
return Result.createBySuccess(user);
}
private User getUserByUsernameAndPassword(String username, String password) {
User record = new User();
record.setUsername(username);
record.setPassword(password);
PageHelper.startPage(1, 1);
List<User> list = userMapper.select(record);
return list.size() == 0 ? null : list.get(0);
}
public Result<User> addUser(String username, String password, String nickname) {
User record = new User();
record.setUsername(username);
List<User> list = userMapper.select(record);
if (!CollectionUtils.isEmpty(list)) {
return Result.createByErrorMessage("用戶已經存在,無法添加");
}
record.setPassword(password);
record.setNickname(nickname);
int resultCount = userMapper.insertSelective(record);
return resultCount == 0 ? Result.createByErrorMessage("添加失敗") : Result.createBySuccess(record);
}
}
這里的userMapper.select(record); userMapper.insertSelective(record);是一個查詢資料庫操作,我們只需要把需要查詢的物件,或者需要插入的物件傳進去就可以了,是不是很方便,當然這里面用到了大量的注解,這些注解就不詳細介紹了,想學的可以自行百度,
注意一點,這里面用到了swagger框架,這個框架是可以幫助我們很方便的進行網路請求測驗:

我們只需要在這里填入資料,就可以看到請求的結果

點擊try it out就可以看到下面的結果集
問題總結:
1.搭建好環境代碼也完整,本地可訪問,其他電腦不可訪問,
java.net.UnknownServiceException: CLEARTEXT communication ** not permitted by network security polic
首先android端要在AndroidManifest.xml中加上:android:usesCleartextTraffic="true",Google表示,為保證用戶資料和設備的安全,針對下一代 Android 系統(Android P) 的應用程式,將要求默認使用加密連接,這意味著 Android P 將禁止 App 使用所有未加密的連接,因此運行 Android P 系統的安卓設備無論是接識訓者發送流量,未來都不能明碼傳輸,需要使用下一代(Transport Layer Security)傳輸層安全協議,而 Android Nougat 和 Oreo 則不受影響,
<application
android:name=".common.MyApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:usesCleartextTraffic="true"
android:theme="@style/Theme.LoginDemo">
一步一步用騰訊云服務器搭建一個tomcat專案,并用外網通過ip訪問專案
2.后端在后期寫的時候,由于使用的框架,在navicat建一個資料庫后,運行代碼會自動建表,這里出現的問題是沒有自動建表,解決:

原來我自己敲的代碼在profiles這一行前面時沒有縮進的,導致自動創建失敗,是因為spring的yml檔案有著嚴格的結構層次,如果層次結構不對那么就會出現意想不到的結果,比如這個,
登陸注冊安卓端完整代碼:
https://github.com/1QQ6/LoginDemo
登陸注冊后端完整代碼:
https://github.com/guangee/login-server-demo
這里我也把注冊登錄功能集成到我的另一個開源專案【音樂播放器】了,想要進一步學習的可以看下哦,
鏈接:https://github.com/1QQ6/TTMusicApp
如果這篇文章有幫助到你,希望能點個贊再走哦!
你們的支持是我走下去的動力!
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/254084.html
標籤:其他
