基本介绍和总体架构
一、 应用介绍
(一)基本介绍
MiniChat是一款聊天软件,你可以通过此软件进行聊天, 本应用集成了融云模块,从而实现集成通讯。主要功能是账号注册,登录,好友添加,好友间发送文字、文件信息。
(二)应用展示
1.登录界面
未输入状态:
输入状态:
2.主界面
朋友栏:
个人栏:
会话列表:
好友添加:
聊天界面:
文件界面:
二、总体架构
1.组成及功能
组成:应用分为三个主要部分,客户端,即时通讯服务端,app 服务器端。
功能:客户端负责前端主要提供用户的功能使用。即时通讯服务端负责应用的通讯服务,负责消息处理,消息的转发。app服务器端负责个人信息维护,好友关系的维护。
2.流程:
三、主要任务
我们需要完成四个部分的工作。
第一,我们需要设计app,app能够通过网络访问app server,从而获取个人信息,好友关系,并且将信息存储到本地;第二,我们需要设计一个服务器,能够接受app的访问,连接数据库,返回用户所需信息;第三,我们需要集成融云客户端,从而实现通信;第四,设计数据库供app服务器进行访问。
四、各结构模块组成
app端:网络信息处理+本地数据库存储+UI+前端时间处理
app server端:servlet设计+数据库访问设计+数据过滤、整理
IM 端:app端集成+聊天信息处理
数据库端:设计关系模式+确定逻辑与物理结构+账户设置
五、开发IDE与相关库
1.IDE
app: Android Studio
app server: Intellij IDEA
融云:Android Studio
数据库:mysql workbench
2.相关库
app:room(本地数据库sqlite工具库)、jetback(Android 官方库)、okhttp(http协议网络连接工具)、glide(图片加载、缓冲库)、GSON(json处理工具)
app server:tomcat(服务器)、GSON、mysql-connector-J
数据库:mysql
IM:MKit、IMLib
需求分析与数据库设计
六、需求分析
此处只做功能需求方面简要概括,实际上真正的需求分析考虑的很多,比如还需要考虑性能需求、安全需求、
其他需求、等方面。
功能需求:
1.用户可以注册账号
2.用户可以添加好友
3.用户可以进行聊天
4.聊天信息可以发送文字、文件。
七、数据库设计
1.关系模型
由于app server只负责用户信息与好友关系维护,因此用于appserver访问的数据库关系模型即user与user组成friend关系,并不复杂。我们只需要设计用户user表、好友关系friend表就好。
2.数据表
friend表:
表结构:
主键:朋友id,用户id
样例数据:
第一行数据表示123456是1234567的朋友,123456的个人名字是123456,nick_name表示1234567为123456起的备注名字
user表:
表结构:
样例数据:
APP界面设计
八、界面结构
采用tab结构,activity与fragment进行结合。
九、主要界面设计
1. 主界面
博主使用了android jetback的导航组件,我们利用导航组件可以轻松的完成tab结构中的fragment跳转。
关于导航组件的介绍,请见下面链接
https://developer.android.google.cn/guide/navigation (官方指南,yyds)
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
android:minHeight="?attr/actionBarSize"
android:theme="?attr/actionBarTheme"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="@+id/nav_view"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar"
app:navGraph="@navigation/mobile_navigation" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/nav_view"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="?android:attr/windowBackground"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:menu="@menu/bottom_nav_menu" />
</androidx.constraintlayout.widget.ConstraintLayout>
2.朋友fragment
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/friendsLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.Friends.FriendsFragment">
<SearchView
android:id="@+id/searchView"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
</SearchView>
<ScrollView
android:id="@+id/scrollView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:fillViewport="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/searchView"
app:layout_constraintVertical_bias="0.0">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/friend_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
3.个人界面
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.home.HomeFragment">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/home_head"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
app:layout_constraintBottom_toTopOf="@id/home_box"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/account"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="15dp"
android:text="113445368"
app:layout_constraintBottom_toTopOf="@+id/name"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:text="张三"
app:layout_constraintStart_toStartOf="@id/account"
app:layout_constraintTop_toBottomOf="@id/account" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/home_box"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/home_head">
<LinearLayout
android:id="@+id/accountView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/account_setting_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="10dp"
android:src="@drawable/rc_cs_default_portrait" />
<TextView
android:id="@+id/account_setting"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="账号管理"
android:textSize="18sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/messageView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/accountView">
<ImageView
android:id="@+id/message_setting_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="10dp"
android:src="@drawable/rc_cs_default_portrait" />
<TextView
android:id="@+id/message_setting"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="消息管理"
android:textSize="18sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/addFriendView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/messageView">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="10dp"
android:src="@drawable/rc_cs_default_portrait" />
<TextView
android:id="@+id/add_friend_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="添加朋友"
android:textSize="18sp" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent">
<Button
android:id="@+id/exit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:text="退出当前账号"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
3.登录界面
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context=".ui.login.LoginActivity">
<EditText
android:id="@+id/username"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="96dp"
android:layout_marginEnd="24dp"
android:hint="电话"
android:inputType="textPhonetic"
android:selectAllOnFocus="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/password"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="24dp"
android:hint="密码"
android:imeActionLabel="@string/action_sign_in_short"
android:imeOptions="actionDone"
android:inputType="textPassword"
android:selectAllOnFocus="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/username" />
<Button
android:id="@+id/rigster"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:layout_marginStart="48dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="48dp"
android:layout_marginBottom="64dp"
android:enabled="false"
android:text="登录"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/password"
app:layout_constraintVertical_bias="0.2" />
<Button
android:id="@+id/rigist_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="注册账号"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<ProgressBar
android:id="@+id/loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginStart="32dp"
android:layout_marginTop="64dp"
android:layout_marginEnd="32dp"
android:layout_marginBottom="64dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@+id/password"
app:layout_constraintStart_toStartOf="@+id/password"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.3" />
</androidx.constraintlayout.widget.ConstraintLayout>
4.注册界面
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".SignUpActivity">
<EditText
android:id="@+id/phone_number"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="96dp"
android:layout_marginEnd="24dp"
android:hint="电话"
android:inputType="textPhonetic"
android:selectAllOnFocus="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/person_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:hint="名称"
android:inputType="textPhonetic"
android:selectAllOnFocus="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/phone_number" />
<EditText
android:id="@+id/password"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="24dp"
android:hint="密码"
android:imeActionLabel="@string/action_sign_in_short"
android:imeOptions="actionDone"
android:inputType="textPassword"
android:selectAllOnFocus="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/person_name" />
<EditText
android:id="@+id/password2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:hint="确认密码"
android:imeActionLabel="@string/action_sign_in_short"
android:imeOptions="actionDone"
android:inputType="textPassword"
android:selectAllOnFocus="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/password" />
<Button
android:id="@+id/rigster"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:layout_marginStart="48dp"
android:layout_marginTop="32dp"
android:layout_marginEnd="48dp"
android:text="注册"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/password2" />
</androidx.constraintlayout.widget.ConstraintLayout>
5.好友添加
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".FriendAddActivity">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="请输入账号ID"
android:textColor="@color/black"
android:textSize="18sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/textInputLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/friend_add_input"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="账号ID" />
</com.google.android.material.textfield.TextInputLayout>
<Button
android:id="@+id/friend_add_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="19dp"
android:text="添加"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textInputLayout" />
</androidx.constraintlayout.widget.ConstraintLayout>
6.文件展示
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".FileActivity">
<ImageView
android:id="@+id/imageView2"
android:layout_width="91dp"
android:layout_height="83dp"
android:layout_marginTop="100dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/rc_file_icon_else" />
<TextView
android:id="@+id/fileName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="文件名"
android:textSize="18sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imageView2" />
<Button
android:id="@+id/other_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:text="用其他应用打开"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/fileName" />
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="50dp"
android:layout_marginTop="13dp"
android:layout_marginEnd="50dp"
android:layout_marginBottom="2dp"
app:layout_constraintBottom_toTopOf="@+id/other_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/fileName" />
<Button
android:id="@+id/download"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:text="开始下载"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/fileName" />
</androidx.constraintlayout.widget.ConstraintLayout>
App架构组成与项目结构组成
十、架构组成
1.ui架构
对于每一个主题,采用了activity+viewmodel+livedata+repository+datasource模式。我们设计应用时您不应在应用组件中存储任何应用数据或状态,并且应用组件不应相互依赖,而是应该注意分离关注点,模型驱动界面。
分离关注点是一个重要原则。一种常见的错误是在一个 Activity 或 Fragment 中编写所有代码。这些基于界面的类应仅包含处理界面和操作系统交互的逻辑。您应使这些类尽可能保持精简,这样可以避免许多与生命周期相关的问题。
另一个重要原则是您应该通过模型驱动界面(最好是持久性模型)。模型是负责处理应用数据的组件。它们独立于应用中的 View 对象和应用组件,因此不受应用的生命周期以及相关的关注点的影响。
持久性是理想之选,原因如下:
①如果 Android 操作系统销毁应用以释放资源,用户不会丢失数据。
②当网络连接不稳定或不可用时,应用会继续工作。
应用所基于的模型类应明确定义数据管理职责,这样将使应用更可测试且更一致。
博主个人认为在这样的架构下有三个优点,第一,结构精简且执行有效;第二点,便于测试,结构进行分离,使得测试更加简单;第三点,代码具有更强的健壮性,在极端环境下,保证应用可使用。
2.其他架构
(1)Room部分
此处不多赘述,主要是数据库的创建,这些均属于基础知识,建议去官方指南学习或者其他学习资源。
(2)网络部分
网络部分基于okhttp3库,Hp内封装了get方法,和post方法,实现http get和post方式。
hp接口,包装各个主题需要使用的网络请求,hpImpl中的类去实现接口。上图中的retrofit也是基于okhttp的,不过博主并未使用。retrofit是一个非常好的库,不过需要注意一些坑。
十一、项目结构
此处只介绍java部分,不再介绍res部分
1.java部分
java部分共为六个模块,data部分是数据的获取模块,db是本地数据库sqlite设计,netservice是网络模块,处理app与服务器的网络访问,ui是前端界面与事件响应设计模块,其余是主体activity。
(1)data模块详解
friends好友关系的数据源处理,home个人信息的数据源处理,login登录的数据源处理,model是使用到的bean模型,Result是一个数据结果类,用来表示数据获取的成功与否。
(2)db
dao表示数据库信息获取的sql处理语句类,model是本地数据库表的构建,appdatabase初始化本地数据库。
(3)nerservice
①Base网络访问的接口
②HpInterface 网络特定访问接口继承于Hp
③HpImpl 是网络接口的实现
④HpModel message是基本信息体,response中所有类均继承于message,request是请求结构的适配体
(4)ui
表示各个主题的viewmodel与fragment设计
(5)util
这个包内包含了一个SHA1的加密算法,可以将一个字符串进行sha1运算。
App的DB设计与网络模块设计
十二、Room本地数据库
(一)引入
关于room库的最新版本请到room的github项目地址查看,关于room库的使用请查看官方指南
def room_version = "2.3.0"
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"
(二)设计
1.appdatebase初始化
AppDataBase.java
@Database(entities = {UserRoom.class, FriendRoom.class},version = 1,exportSchema = false)
public abstract class APPDataBase extends RoomDatabase {
public abstract UserDao userDao();
public abstract FriendDao friendDao();
}
2.dao设计
UserDao.java
@Dao
public interface UserDao {
@Query("select * from user")
List<UserRoom> getAll();
@Insert
Completable insertOne(UserRoom userRoom);
@Update
Completable updateOne(UserRoom userRoom);
@Query("delete from user where uid = :uid")
Completable delete(String uid);
@Query("select * from user where uid = :uid")
Single<UserRoom> getById(String uid);
@Query("update user set is_log=0 where is_log=1")
Completable updateLog();
@Query("select * from user where is_log=1")
Single<UserRoom> getLogUser();
}
其余Dao将不介绍,请查看本人github项目。
3.表设计
UserRoom.java
@Entity(tableName = "user")
public class UserRoom {
@PrimaryKey
@ColumnInfo(name = "uid")
@NonNull
public String uid = null;
@ColumnInfo(name = "user_name")
public String userName;
@ColumnInfo(name = "is_log")
public int isLog;
@Override
public String toString() {
return "UserRoom{" +
"uid='" + uid + '\'' +
", userName='" + userName + '\'' +
", isLog=" + isLog +
'}';
}
}
其余不做介绍,请查看本人github项目。
十三、网络模块设计
(一)post数据流
json数据 解析json数据
app ----------->servlert--------------->执行数据访问
app <-----------servlert<---------------数据库
接受json数据并解析 数据集转json
对象流:Request—>json—>requestModel–>database—>dbmodel–>responsemodel—>json---->response
(二)okhttp引入
implementation 'com.squareup.okhttp3:okhttp:4.9.0'
(三)Base设计
1.HttpGet.java
public class HttpGet implements Callable<String> {
//要get的url
private String url;
public HttpGet(String url) {
//初始化
this.url = url;
}
@Override
public String call() throws Exception {
//获得okHttp客户端
OkHttpClient client=new OkHttpClient();
Log.d("httpGet",url+" start");
//构建request
Request request = new Request.Builder()
.url(url)
.build();
Log.d("httpGet",url+" build");
//执行访问
Response response=client.newCall(request).execute();
Log.d("httpGet",url+" execute");
return Objects.requireNonNull(response.body()).string();
}
}
2.HttpPost.java
public class HttpPost implements Callable<String> {
private String url;
private String json;
private static final MediaType JSON
= MediaType.get("application/json; charset=utf-8");
public HttpPost(String url, String json) {
this.url = url;
this.json = json;
}
@Override
public String call() throws Exception {
//获得okHttp客户端
OkHttpClient client=new OkHttpClient();
Log.d("httpPost",url+" start for json:"+json);
RequestBody body = RequestBody.create(JSON, json);
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
Log.d("httpPost",url+" build:"+body.contentType());
Response response= client.newCall(request).execute();
Log.d("httpPost",url+" receive");
return Objects.requireNonNull(response.body()).string();
}
}
3.HttpRongPost.java
public class HttpPostRong implements Callable<String> {
private String url;
private String json;
private RequestBody body;
public HttpPostRong(String url, RequestBody requestBody) {
this.url = url;
this.body=requestBody;
}
@Override
public String call() throws Exception {
String app_secret="xxxxxx";
String nonce="131415";
String timestamp=String.valueOf(System.currentTimeMillis());
//进行sha1计算
String signature= SHA1.sha1(app_secret+nonce+timestamp);
//获得okHttp客户端
OkHttpClient client=new OkHttpClient();
Log.d("httpPost",url+" start for body:"+body.contentType());
Request request = new Request.Builder()
.addHeader("App-Key","8luwapkv86p3l")
.addHeader("Nonce",nonce)
.addHeader("Timestamp",timestamp)
.addHeader("Signature",signature)
.url(url)
.post(body)
.build();
Log.d("httpPost",url+" build:"+request.toString());
Response response= client.newCall(request).execute();
Log.d("httpPost",url+" receive");
return Objects.requireNonNull(response.body()).string();
}
}
4.Hp.java
public interface Hp {
Gson gson=new Gson();
default String get(final String url) throws IOException, ExecutionException, InterruptedException {
//execute get 执行get
HttpGet get=new HttpGet(url);
//thread schedule 线程调用
ExecutorService service= Executors.newFixedThreadPool(1);
Future<String> future_get=service.submit(get);
return future_get.get();
}
default String post(final String url,final String json) throws IOException, ExecutionException, InterruptedException {
//execute post 执行post
HttpPost post=new HttpPost(url,json);
//thread schedule 线程调用
ExecutorService service= Executors.newFixedThreadPool(1);
Future<String> future_post=service.submit(post);
return future_post.get();
}
default String postRong(final String url, final RequestBody body) throws IOException, ExecutionException, InterruptedException {
//excute RongCloud post 执行融云的post
HttpPostRong post=new HttpPostRong(url,body);
//thread schedule 线程调用
ExecutorService service= Executors.newFixedThreadPool(1);
Future<String> future_post=service.submit(post);
return future_post.get();
}
}
AppUi设计
十四、主界面设计
(一)BottomActivity主界面类
此类集成了融云Im服务,对融云服务器进行连接,另外初始化viewmodel,设置导航,便于切换。
public class BottomActivity extends AppCompatActivity {
//最主要的activity
private BottomViewModel bottomViewModel;
public static void actionStart(Context context,String uid,String token){
Intent intent=new Intent(context,BottomActivity.class);
intent.putExtra("uid",uid);
intent.putExtra("token",token);
context.startActivity(intent);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_bottom);
bottomViewModel=new ViewModelProvider(this,new ViewModelProvider.NewInstanceFactory()).get(BottomViewModel.class);
Intent intent=getIntent();
String uid=intent.getStringExtra("uid");
String token=intent.getStringExtra("token");
bottomViewModel.getUid().setValue(uid);
bottomViewModel.setToken(token);
//Rong
RongIM.connect(token, new RongIMClient.ConnectCallback() {
@Override
public void onSuccess(String s) {
}
@Override
public void onError(RongIMClient.ConnectionErrorCode connectionErrorCode) {
if(connectionErrorCode.equals(RongIMClient.ConnectionErrorCode.RC_CONN_TOKEN_INCORRECT)) {
//从 APP 服务获取新 token,并重连
} else {
//无法连接 IM 服务器,请根据相应的错误码作出对应处理
}
}
@Override
public void onDatabaseOpened(RongIMClient.DatabaseOpenStatus databaseOpenStatus) {
//消息数据库打开,可以进入到主页面
}
});
RongConfigCenter.conversationConfig().setConversationClickListener(new ConversationClickListener() {
@Override
public boolean onUserPortraitClick(Context context, Conversation.ConversationType conversationType, UserInfo userInfo, String s) {
return false;
}
@Override
public boolean onUserPortraitLongClick(Context context, Conversation.ConversationType conversationType, UserInfo userInfo, String s) {
return false;
}
@Override
public boolean onMessageClick(Context context, View view, Message message) {
//文件消息处理
/*if(message.getObjectName().equals("RC:FileMsg")){
FileMessage fileMessage=(FileMessage)message.getContent();
//FileActivity.ActionStart(context,fileMessage);
return false;
}*/
return false;
}
@Override
public boolean onMessageLongClick(Context context, View view, Message message) {
return false;
}
@Override
public boolean onMessageLinkClick(Context context, String s, Message message) {
return false;
}
@Override
public boolean onReadReceiptStateClick(Context context, Message message) {
return false;
}
});
bottomViewModel.getIs_logout().observe(this, new Observer<Boolean>() {
@Override
public void onChanged(Boolean aBoolean) {
if(aBoolean){
LoginActivity.ActionStart(BottomActivity.this);
finish();
}
}
});
Log.d("bottomViewModel",bottomViewModel.toString());
//导航组件的处理
BottomNavigationView navView = findViewById(R.id.nav_view);
// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations. id要一一对应
//标题
AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(
R.id.navigation_conversation,R.id.navigation_friends,R.id.navigation_home )
.build();
NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment);
NavController navController = navHostFragment.getNavController();
//NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
//设置toolbar
NavigationUI.setupWithNavController(findViewById(R.id.toolbar),navController,appBarConfiguration);
//设置导航图
NavigationUI.setupWithNavController(navView, navController);
}
}
(二)BottomViewModel类
public class BottomViewModel extends ViewModel {
private MutableLiveData<String> uid;
private MutableLiveData<String> u_name;
private MutableLiveData<Boolean> is_logout;
private String token;
public BottomViewModel(){
uid=new MutableLiveData<>();
u_name=new MutableLiveData<>();
is_logout=new MutableLiveData<>();
is_logout.setValue(false);
}
public MutableLiveData<String> getUid() {
return uid;
}
public MutableLiveData<String> getU_name() {
return u_name;
}
public MutableLiveData<Boolean> getIs_logout() {
return is_logout;
}
public void logout(){
is_logout.setValue(true);
}
public void setToken(String token) {
this.token=token;
}
public String getToken() {
return token;
}
@Override
public String toString() {
return "BottomViewModel{" +
"uid=" + uid.getValue() +
", u_name=" + u_name.getValue() +
", is_logout=" + is_logout.getValue() +
", token='" + token + '\'' +
'}';
}
}
十五、个人主页
(一)HomeFragment类
视图
public class HomeFragment extends Fragment implements LifecycleOwner {
private HomeViewModel homeViewModel;
public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_home, container, false);
homeViewModel =
new ViewModelProvider(this,new HomeViewModelFactory()).get(HomeViewModel.class);
BottomViewModel bottomViewModel=new ViewModelProvider(requireActivity()).get(BottomViewModel.class);
final TextView accountView = root.findViewById(R.id.account);
final TextView nameView=root.findViewById(R.id.name);
final View a_s_view=root.findViewById(R.id.accountView);
final View m_s_view=root.findViewById(R.id.messageView);
final View addFriendView=root.findViewById(R.id.addFriendView);
final Button exitButton=root.findViewById(R.id.exit);
//视图根据viewModel中的liveData监测
homeViewModel.getUid().observe(getViewLifecycleOwner(), new Observer<String>() {
@Override
public void onChanged(String s) {
accountView.setText(s);
}
});
homeViewModel.getU_name().observe(getViewLifecycleOwner(), new Observer<String>() {
@Override
public void onChanged(String s) {
nameView.setText(s);
}
});
//activity的启动
a_s_view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
AccountSettingActivity.ActionStart(getContext());
}
});
m_s_view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
MessageSettingsActivity.ActionStart(getContext());
}
});
addFriendView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
FriendAddActivity.ActionStart(getContext(),bottomViewModel.getUid().getValue());
}
});
//退出
exitButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
bottomViewModel.logout();
RongIM.getInstance().logout();
}
});
return root;
}
}
(二)HomeViewModel类
viewmodel 利用repository类进行构建,连接viewmodel数据源。
public class HomeViewModel extends ViewModel {
private MutableLiveData<String> uid;
private MutableLiveData<String> u_name;
private HomeRepository homeRepository;
public HomeViewModel(HomeRepository homeRepository) {
uid=new MutableLiveData<>();
u_name=new MutableLiveData<>();
this.homeRepository=homeRepository;
init();
}
public MutableLiveData<String> getUid() {
return uid;
}
public MutableLiveData<String> getU_name() {
return u_name;
}
private void init(){
//初始化
Result<User> userResult=homeRepository.getUser();
if(userResult instanceof Result.Success){
User user=((Result.Success<User>) userResult).getData();
uid.setValue(user.getUid());
u_name.setValue(user.getName());
}else {
uid.setValue("unknown error");
u_name.setValue("unknown error");
}
}
}
(三)ViewFactory
public class HomeViewModelFactory implements ViewModelProvider.Factory {
@NonNull
@Override
@SuppressWarnings("unchecked")
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
if (modelClass.isAssignableFrom(HomeViewModel.class)) {
return (T) new HomeViewModel(HomeRepository.getInstance(new HomeDataSource()));
} else {
throw new IllegalArgumentException("Unknown ViewModel class");
}
}
}
十六、好友
(一)FriendFragment
public class FriendsFragment extends Fragment {
private FriendsViewModel mViewModel;
public static FriendsFragment newInstance() {
return new FriendsFragment();
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View root=inflater.inflate(R.layout.fragment_friends, container, false);
//获取activity的viewModel
BottomViewModel bottomViewModel=new ViewModelProvider(requireActivity()).get(BottomViewModel.class);
//初始化viewmodel,获取朋友
mViewModel=new ViewModelProvider(this,new FriendsViewModelFactory(bottomViewModel.getUid().getValue())).get(FriendsViewModel.class);
mViewModel.init(bottomViewModel.getUid().getValue());
//recyclerView构图
RecyclerView recyclerView=root.findViewById(R.id.friend_list);
LinearLayoutManager manager=new LinearLayoutManager(this.getContext());
List<Friend> friendList=mViewModel.getFriendListData().getValue();
Log.d("friendList",String.valueOf(friendList.size()));
if(friendList==null){
friendList=new ArrayList<>();
}
FriendAdapter adapter=new FriendAdapter(friendList);
recyclerView.setLayoutManager(manager);
recyclerView.setAdapter(adapter);
mViewModel.getFriendListData().observe(getViewLifecycleOwner(), new Observer<List<Friend>>() {
@Override
public void onChanged(List<Friend> friendList) {
adapter.notifyAfterAdd(friendList);
}
});
return root;
}
}
(二)ViewModel
public class FriendsViewModel extends ViewModel {
public MutableLiveData<List> getFriendListData() {
return friendListData;
}
public FriendRepository getRepository() {
return repository;
}
private final MutableLiveData<List<Friend>> friendListData;
private final FriendRepository repository;
public FriendsViewModel(FriendRepository repository) {
friendListData=new MutableLiveData<>();
this.repository=repository;
}
public void init(String uid){
//初始化
Result<List<Friend>> result=repository.getFriends(uid);
if(result instanceof Result.Success){
List<Friend> friendList= ((Result.Success<List<Friend>>) result).getData();
friendListData.setValue(friendList);
}
//test
/*List<Friend> friendList=new ArrayList<>();
friendList.add(new Friend("1234","1234567","xiaoming","nick1"));
friendList.add(new Friend("12345","1234567","xiaming",null));
friendListData.setValue(friendList);*/
}
}
(三)ViewmodelFactory
public class FriendsViewModelFactory implements ViewModelProvider.Factory{
private String uid;
public FriendsViewModelFactory(String uid) {
this.uid = uid;
}
@NonNull
@Override
@SuppressWarnings("unchecked")
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
if (modelClass.isAssignableFrom(FriendsViewModel.class)) {
return (T) new FriendsViewModel(FriendRepository.getInstance(new FriendDataSource()));
} else {
throw new IllegalArgumentException("Unknown ViewModel class");
}
}
}
(四)FriendAdapter
public class FriendAdapter extends RecyclerView.Adapter<FriendAdapter.ViewHolder> {
private List<Friend> friendList;
public static class ViewHolder extends RecyclerView.ViewHolder{
private final TextView nameView;
private final View itemView;
public ViewHolder(@NonNull View itemView) {
super(itemView);
this.itemView=itemView;
nameView=(TextView)itemView.findViewById(R.id.friend_name);
}
public TextView getNameView() {
return nameView;
}
public View getItemView() {
return itemView;
}
}
public void notifyAfterAdd(List<Friend> friends){
friendList=friends;
notifyDataSetChanged();
}
public FriendAdapter(List<Friend> friendList) {
this.friendList=friendList;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_friend, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
//名称的设置
if(friendList.get(position).getNick_name()==null){
holder.getNameView().setText(friendList.get(position).getF_name());
}else {
holder.getNameView().setText(friendList.get(position).getNick_name());
}
holder.getItemView().setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Conversation.ConversationType conversationType = Conversation.ConversationType.PRIVATE;
String targetId = friendList.get(position).getFid();
String title =friendList.get(position).getF_name();
Bundle bundle = new Bundle();
if (!TextUtils.isEmpty(title)) {
bundle.putString(RouteUtils.TITLE, title); //会话页面标题
//bundle.putLong(RouteUtils.INDEX_MESSAGE_TIME, fixedMsgSentTime); //打开会话页面时的默认跳转位置,如果不配置将跳转到消息列表底部
}
RouteUtils.routeToConversationActivity(view.getContext(), conversationType, targetId, bundle);//跳转会话页面
}
});
}
@Override
public int getItemCount() {
return friendList.size();
}
}
十七、注册
Activity
public class SignUpActivity extends AppCompatActivity {
//注册活动
public static void ActionStart(Context context){
Intent intent=new Intent(context,SignUpActivity.class);
context.startActivity(intent);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sign_up);
final EditText phone=findViewById(R.id.phone_number);
final EditText name=findViewById(R.id.person_name);
final EditText password=findViewById(R.id.password);
final EditText password2=findViewById(R.id.password2);
final Button button=findViewById(R.id.rigster);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String phone_v=phone.getText().toString();
String name_v=name.getText().toString();
String password_v=password.getText().toString();
String password_v2=password2.getText().toString();
if(phone_v.equals("")||name_v.equals("")||password_v.equals("")||password_v2.equals("")){
Toast.makeText(SignUpActivity.this,"不能为空",Toast.LENGTH_SHORT).show();
}else {
if(!password_v.equals(password_v2)){
Toast.makeText(SignUpActivity.this,"密码不同",Toast.LENGTH_SHORT).show();
}else {
try {
boolean isOk=new SignUpImpl().signUp(phone_v,password_v2,name_v);
if(isOk){
Toast.makeText(getApplicationContext(),"注册成功",Toast.LENGTH_SHORT).show();
SignUpActivity.this.finish();
}else {
Toast.makeText(getApplicationContext(),"该账号已注册",Toast.LENGTH_SHORT).show();
}
} catch (HpException hpException) {
Toast.makeText(getApplicationContext(),"服务器错误hp",Toast.LENGTH_SHORT).show();
} catch (InterruptedException e) {
e.printStackTrace();
Toast.makeText(getApplicationContext(),"服务器错误interrupt",Toast.LENGTH_SHORT).show();
} catch (ExecutionException e) {
e.printStackTrace();
Toast.makeText(getApplicationContext(),"服务器错误execu",Toast.LENGTH_SHORT).show();
} catch (IOException e) {
e.printStackTrace();
Toast.makeText(getApplicationContext(),"服务器错误io",Toast.LENGTH_SHORT).show();
}
}
}
}
});
}
}
十八、登录
(一)LoginActivity
public class LoginActivity extends AppCompatActivity {
public static void ActionStart(Context context){
Intent intent=new Intent(context,LoginActivity.class);
context.startActivity(intent);
}
private LoginViewModel loginViewModel;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
loginViewModel = new ViewModelProvider(this, new LoginViewModelFactory())
.get(LoginViewModel.class);
final EditText usernameEditText = findViewById(R.id.username);
final EditText passwordEditText = findViewById(R.id.password);
final Button loginButton = findViewById(R.id.rigster);
final ProgressBar loadingProgressBar = findViewById(R.id.loading);
final Button signup_btn=findViewById(R.id.rigist_btn);
loginViewModel.getLoginFormState().observe(this, new Observer<LoginFormState>() {
@Override
public void onChanged(@Nullable LoginFormState loginFormState) {
if (loginFormState == null) {
return;
}
loginButton.setEnabled(loginFormState.isDataValid());
if (loginFormState.getUsernameError() != null) {
usernameEditText.setError(getString(loginFormState.getUsernameError()));
}
if (loginFormState.getPasswordError() != null) {
passwordEditText.setError(getString(loginFormState.getPasswordError()));
}
}
});
loginViewModel.getLoginResult().observe(this, new Observer<LoginResult>() {
@Override
public void onChanged(@Nullable LoginResult loginResult) {
if (loginResult == null) {
return;
}
loadingProgressBar.setVisibility(View.GONE);
if (loginResult.getError() != null) {
//出现网络异常
showLoginFailed(loginResult.getError());
setResult(Activity.RESULT_CANCELED);
}
if (loginResult.getSuccess() != null) {
LoggedInUserView data=loginResult.getSuccess();
if(data.isOk()){
//密码正确
updateUiWithUser(data.getUid(),data.getToken());
setResult(Activity.RESULT_OK);
//Complete and destroy login activity once successful
finish();
}else {
//密码错误
Toast.makeText(getApplicationContext(), "password wrong", Toast.LENGTH_SHORT).show();
}
}
}
});
TextWatcher afterTextChangedListener = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// ignore
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// ignore
}
@Override
public void afterTextChanged(Editable s) {
loginViewModel.loginDataChanged(usernameEditText.getText().toString(),
passwordEditText.getText().toString());
}
};
usernameEditText.addTextChangedListener(afterTextChangedListener);
passwordEditText.addTextChangedListener(afterTextChangedListener);
passwordEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_DONE) {
loginViewModel.login(usernameEditText.getText().toString(),
passwordEditText.getText().toString());
}
return false;
}
});
loginButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
loadingProgressBar.setVisibility(View.VISIBLE);
loginViewModel.login(usernameEditText.getText().toString(),
passwordEditText.getText().toString());
}
});
signup_btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
SignUpActivity.ActionStart(LoginActivity.this);
}
});
}
private void updateUiWithUser(String uid,String token) {
//登录成功应该转至主界面,并传递登录的id
BottomActivity.actionStart(LoginActivity.this,uid,token);
}
private void showLoginFailed(@StringRes Integer errorString) {
Toast.makeText(getApplicationContext(), errorString, Toast.LENGTH_SHORT).show();
}
}
(二)ViewModel类
public class LoginViewModel extends ViewModel {
private MutableLiveData<LoginFormState> loginFormState = new MutableLiveData<>();
private MutableLiveData<LoginResult> loginResult = new MutableLiveData<>();
private LoginRepository loginRepository;
LoginViewModel(LoginRepository loginRepository) {
this.loginRepository = loginRepository;
}
LiveData<LoginFormState> getLoginFormState() {
return loginFormState;
}
LiveData<LoginResult> getLoginResult() {
return loginResult;
}
public void login(String username, String password) {
// can be launched in a separate asynchronous job
Result<LoggedInUser> result = loginRepository.login(username, password);
if (result instanceof Result.Success) {
LoggedInUser data = ((Result.Success<LoggedInUser>) result).getData();
loginResult.setValue(new LoginResult(new LoggedInUserView(data.isOk(),data.getUserId(),data.getDisplayName(),data.getToken())));
} else {
loginResult.setValue(new LoginResult(R.string.login_failed));
}
}
public void loginDataChanged(String username, String password) {
if (!isUserNameValid(username)) {
loginFormState.setValue(new LoginFormState(R.string.invalid_username, null));
} else if (!isPasswordValid(password)) {
loginFormState.setValue(new LoginFormState(null, R.string.invalid_password));
} else {
loginFormState.setValue(new LoginFormState(true));
}
}
// A placeholder username validation check
private boolean isUserNameValid(String username) {
if (username == null) {
return false;
}
return Patterns.PHONE.matcher(username).matches();
}
// A placeholder password validation check
private boolean isPasswordValid(String password) {
return password != null && password.trim().length() > 5;
}
}
(三)其他类
public class LoginViewModelFactory implements ViewModelProvider.Factory {
@NonNull
@Override
@SuppressWarnings("unchecked")
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
if (modelClass.isAssignableFrom(LoginViewModel.class)) {
return (T) new LoginViewModel(LoginRepository.getInstance(new LoginDataSource()));
} else {
throw new IllegalArgumentException("Unknown ViewModel class");
}
}
}
class LoginResult {
@Nullable
private LoggedInUserView success;
@Nullable
private Integer error;
LoginResult(@Nullable Integer error) {
this.error = error;
}
LoginResult(@Nullable LoggedInUserView success) {
this.success = success;
}
@Nullable
LoggedInUserView getSuccess() {
return success;
}
@Nullable
Integer getError() {
return error;
}
}
class LoginFormState {
@Nullable
private Integer usernameError;
@Nullable
private Integer passwordError;
private boolean isDataValid;
LoginFormState(@Nullable Integer usernameError, @Nullable Integer passwordError) {
this.usernameError = usernameError;
this.passwordError = passwordError;
this.isDataValid = false;
}
LoginFormState(boolean isDataValid) {
this.usernameError = null;
this.passwordError = null;
this.isDataValid = isDataValid;
}
@Nullable
Integer getUsernameError() {
return usernameError;
}
@Nullable
Integer getPasswordError() {
return passwordError;
}
boolean isDataValid() {
return isDataValid;
}
}
class LoggedInUserView {
private boolean isOk;
private String uid;
private String displayName;
private String token;
//... other data fields that may be accessible to the UI
public LoggedInUserView(boolean isOk, String uid, String displayName, String token) {
this.isOk = isOk;
this.uid = uid;
this.displayName = displayName;
this.token = token;
}
public void setOk(boolean ok) {
isOk = ok;
}
public void setUid(String uid) {
this.uid = uid;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public LoggedInUserView(boolean isOk) {
this.isOk = isOk;
}
public boolean isOk() {
return isOk;
}
public String getDisplayName() {
return displayName;
}
public String getUid() {
return uid;
}
}
十九、文件预览
1.Activity
public class FileActivity extends AppCompatActivity {
//文件activity
public static void ActionStart(Context context, FileMessage fileMessage){
Intent intent=new Intent(context,FileActivity.class);
Log.d("messageObject",fileMessage.getFileUrl().toString());
intent.putExtra("fileMessage",fileMessage);
context.startActivity(intent);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_file);
final TextView fileNameView=findViewById(R.id.fileName);
final Button button=findViewById(R.id.other_button);
final Button down_button=findViewById(R.id.download);
final ProgressBar progressBar=findViewById(R.id.progressBar);
Intent intent=getIntent();
FileMessage fileMessage=intent.getParcelableExtra("fileMessage");
String fileMessageName= fileMessage.getName();
String saveDir="com.example.minichat/files";
if(fileMessage.getLocalPath()==null){
//表示不是本地文件
File file=new File(Environment.getExternalStorageDirectory().getAbsolutePath(),saveDir+"/"+fileMessageName);
Log.d("fileAbsolute",file.getAbsolutePath());
if(!file.exists()){
//未下载过
down_button.setVisibility(View.VISIBLE);
progressBar.setVisibility(View.VISIBLE);
button.setVisibility(View.GONE);
}else{
down_button.setVisibility(View.GONE);
progressBar.setVisibility(View.GONE);
button.setVisibility(View.VISIBLE);
}
}else{
Log.d("fileLocalPath",fileMessage.getLocalPath().toString());
down_button.setVisibility(View.GONE);
progressBar.setVisibility(View.GONE);
button.setVisibility(View.VISIBLE);
}
down_button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
DownloadUtil.get().download(fileMessage.getFileUrl().toString(), saveDir, fileMessageName,new DownloadUtil.OnDownloadListener() {
@Override
public void onDownloadSuccess() {
FileActivity.this.runOnUiThread(new Runnable() {
@Override
public void run() {
down_button.setVisibility(View.GONE);
button.setVisibility(View.VISIBLE);
progressBar.setVisibility(View.GONE);
}
});
}
@Override
public void onDownloading(int progress) {
FileActivity.this.runOnUiThread(new Runnable() {
@Override
public void run() {
progressBar.setProgress(progress);
down_button.setText("下载中");
down_button.setEnabled(false);
}
});
}
@Override
public void onDownloadFailed() {
FileActivity.this.runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(FileActivity.this,"下载失败",Toast.LENGTH_LONG).show();
}
});
}
});
}
});
fileNameView.setText(fileMessage.getName());
Log.d("fileMessageRemoteUrl",fileMessage.getFileUrl().toString());
Log.d("fileType",fileMessage.getType());
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//用其他应用打开文件
if(fileMessage.getLocalPath()!=null){
openFile(new File(fileMessage.getLocalPath().getPath()),FileActivity.this);
}else {
openFile(new File(Environment.getExternalStorageDirectory().getAbsolutePath(),saveDir+"/"+fileMessageName),FileActivity.this);
}
}
});
}
private void openFile(File file, Context mContext) {
try {
Intent intent = new Intent();
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//设置intent的Action属性
intent.setAction(Intent.ACTION_VIEW);
//获取文件file的MIME类型
String type = getMIMEType(file);
Log.d("fielType",type);
//设置intent的data和Type属性。android 7.0以上crash,改用provider
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Uri fileUri = FileProvider.getUriForFile(mContext, mContext.getPackageName() + ".provider", file);//android 7.0以上
intent.setDataAndType(fileUri, type);
grantUriPermission(mContext, fileUri, intent);
} else {
intent.setDataAndType(/*uri*/Uri.fromFile(file), type);
}
//跳转
mContext.startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
}
}
private static void grantUriPermission(Context context, Uri fileUri, Intent intent) {
List<ResolveInfo> resInfoList = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
String packageName = resolveInfo.activityInfo.packageName;
context.grantUriPermission(packageName, fileUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
}
/**
* 根据文件后缀名获得对应的MIME类型。
*
* @param file
*/
private static String getMIMEType(File file) {
String type = "*/*";
String fName = file.getName();
//获取后缀名前的分隔符"."在fName中的位置。
int dotIndex = fName.lastIndexOf(".");
if (dotIndex < 0) {
return type;
}
/* 获取文件的后缀名 */
String end = fName.substring(dotIndex, fName.length()).toLowerCase();
if (end == "") return type;
//在MIME和文件类型的匹配表中找到对应的MIME类型。
for (int i = 0; i < MIME_MapTable.length; i++) { //MIME_MapTable??在这里你一定有疑问,这个MIME_MapTable是什么?
if (end.equals(MIME_MapTable[i][0]))
type = MIME_MapTable[i][1];
}
return type;
}
private static final String[][] MIME_MapTable = {
//{后缀名, MIME类型}
{".3gp", "video/3gpp"},
{".apk", "application/vnd.android.package-archive"},
{".asf", "video/x-ms-asf"},
{".avi", "video/x-msvideo"},
{".bin", "application/octet-stream"},
{".bmp", "image/bmp"},
{".c", "text/plain"},
{".class", "application/octet-stream"},
{".conf", "text/plain"},
{".cpp", "text/plain"},
{".doc", "application/msword"},
{".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"},
{".xls", "application/vnd.ms-excel"},
{".xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"},
{".exe", "application/octet-stream"},
{".gif", "image/gif"},
{".gtar", "application/x-gtar"},
{".gz", "application/x-gzip"},
{".h", "text/plain"},
{".htm", "text/html"},
{".html", "text/html"},
{".jar", "application/java-archive"},
{".java", "text/plain"},
{".jpeg", "image/jpeg"},
{".jpg", "image/jpeg"},
{".js", "application/x-javascript"},
{".log", "text/plain"},
{".m3u", "audio/x-mpegurl"},
{".m4a", "audio/mp4a-latm"},
{".m4b", "audio/mp4a-latm"},
{".m4p", "audio/mp4a-latm"},
{".m4u", "video/vnd.mpegurl"},
{".m4v", "video/x-m4v"},
{".mov", "video/quicktime"},
{".mp2", "audio/x-mpeg"},
{".mp3", "audio/x-mpeg"},
{".mp4", "video/mp4"},
{".mpc", "application/vnd.mpohun.certificate"},
{".mpe", "video/mpeg"},
{".mpeg", "video/mpeg"},
{".mpg", "video/mpeg"},
{".mpg4", "video/mp4"},
{".mpga", "audio/mpeg"},
{".msg", "application/vnd.ms-outlook"},
{".ogg", "audio/ogg"},
{".pdf", "application/pdf"},
{".png", "image/png"},
{".pps", "application/vnd.ms-powerpoint"},
{".ppt", "application/vnd.ms-powerpoint"},
{".pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"},
{".prop", "text/plain"},
{".rc", "text/plain"},
{".rmvb", "audio/x-pn-realaudio"},
{".rtf", "application/rtf"},
{".sh", "text/plain"},
{".tar", "application/x-tar"},
{".tgz", "application/x-compressed"},
{".txt", "text/plain"},
{".wav", "audio/x-wav"},
{".wma", "audio/x-ms-wma"},
{".wmv", "audio/x-ms-wmv"},
{".wps", "application/vnd.ms-works"},
{".xml", "text/plain"},
{".z", "application/x-compress"},
{".zip", "application/x-zip-compressed"},
{"", "*/*"}
};
}
App数据源处理
二十、基本构成介绍
当前端需要调动后端执行一个动作时,前端ui将动作执行委托给viewmodel,viewmodel调用respository中执行,在repository中根据情况选择从本地存储读取还是从网络读取,一般情况下优先选择存储读取。repository应该是单例的,以免造成存在多个repository存在,造成本地数据库与网络数据混淆,数据库数据不一致。
在repository对于本地数据和网络数据的处理最好的做法是在repository设置读取本地数据库方法或者委托给其他类执行数据库读取如Dao,然后在repository设置一个方法,由该方法决定从datasource(remote数据源即网络数据源)还是从本地数据库读取,我们最好不要在datasource类中设置数据的方法。
如:一个聊天应用中,界面显示个人信息,我们需要的个人信息相关数据,一般情况下,不会更新,因此选择优先选择从数据库读取是个更好的选择。当ui需要数据时,viewmodel可调用getfriends方法,viewmodel委托给repository执行,repository选择本地数据库还调用datasource类执行。
repository如何判断是最新消息呢?我们可以在本地保留一个数据版本号,当读取数据时应向服务器发送最新版本号,当服务器发现版本号不同时,服务器返回最新数据和最新的版本号。根据返回情况,我们选择更新本地数据库,还是直接读取本地数据库。我们要使用一个较短时间能完成的响应尽可能的替代较长时间完成的响应。通过网络获取大量数据是极为耗时的,而且还可能在网络不好的情况下造成数据丢失,造成严重后果,因此我们应尽可能地减少这种操作。我们这样即可以减少服务器的负担,也能给用户更好的体验。
至于上面的代码实现,还请小伙伴们自己思考。
下面是我的mini聊天app中部分datasource与datarepository的实现。
二十一、datasource与repository
(一)朋友
1.FriendAddDataSource
public class FriendAddDataSource {
Result<Friend> addFriend(String uid,String f_id){
//net
try {
FriendAddResponse response = new FriendHpImpl().addFriend(uid, f_id);
if(response.getCode()>0){
Friend friend=new Friend(response.getId(),uid,response.getName(),null);
return new Result.Success<>(friend);
}else {
return new Result.Error(new Exception("had added"));
}
}catch (HpException hpException){
return new Result.Error(new Exception("NetService wsa shut down"));
}
}
}
2. FriendAddRepository
public class FriendAddRepository {
private static volatile FriendAddRepository instance;
private FriendAddDataSource dataSource;
public static FriendAddRepository getInstance(FriendAddDataSource source) {
if(instance==null){
instance=new FriendAddRepository(source);
}
return instance;
}
public FriendAddRepository(FriendAddDataSource dataSource) {
this.dataSource = dataSource;
}
public void setFriend(Friend friend){
//load into local storage
FriendRoom friendRoom=new FriendRoom(friend.getFid(),friend.getUid(),friend.getF_name(),friend.getNick_name());
AsyncTask.execute(new Runnable() {
@Override
public void run() {
APPDataBase db= App.getInstance().getDataBase();
db.friendDao().insertFriendOne(friendRoom).blockingSubscribe();
}
});
}
public Result<Friend> addFriend(String uid,String f_id){
Result<Friend> friendResult=dataSource.addFriend(uid,f_id);
if(friendResult instanceof Result.Success){
setFriend(((Result.Success<Friend>) friendResult).getData());
}
return friendResult;
}
}
3.FriendDataSource
public class FriendDataSource {
private LiveData<List<FriendRoom>> friendsLiveData;
private final FriendDao dao;
public FriendDataSource() {
dao= App.getInstance().getDataBase().friendDao();
//friendsLiveData=dao.getFriendsByUId(uid);
}
public Result<List<Friend>> getFriends(String uid){
List<Friend> friendList=new ArrayList<>();
//if (friendsLiveData==null||friendsLiveData.getValue()==null){
List<FriendRoom> friendRoomList=new ArrayList<>();
//remote
try{
List<FriendResponse> responseList=new FriendHpImpl().getFriendsById(uid);
for(FriendResponse response:responseList){
if (response.getCode()>0){
friendList.add(new Friend(response.getFid(),response.getUid(),response.getF_name(),response.getNick_name()));
friendRoomList.add(new FriendRoom(response.getFid(),response.getUid(),response.getF_name(),response.getNick_name()));
}else {
return new Result.Error(new IOException("Error fetching friends"));
}
}
//load into room
AsyncTask.execute(new Runnable() {
@Override
public void run() {
if(friendRoomList.size()>0){
dao.insertFriends(friendRoomList).blockingSubscribe();
friendsLiveData=dao.getFriendsByUId(uid);
}
}
});
}catch (HpException hp){
return new Result.Error(new Exception("NetService was shut down."));
}
for (Friend f:friendList
) {
Log.d("friendListItme",f.toString());
}
// }else {
// //room
// List<FriendRoom> friendRoomList=friendsLiveData.getValue();
// for (FriendRoom friendRoom:friendRoomList){
// friendList.add(new Friend(friendRoom.fid,friendRoom.uid,friendRoom.f_name,friendRoom.nick_name));
// }
// }
return new Result.Success<>(friendList);
}
public LiveData<List<FriendRoom>> getFriendsLiveData() {
return friendsLiveData;
}
}
4.FriendRepository
public class FriendRepository {
private static volatile FriendRepository instance;
private FriendDataSource dataSource;
public static FriendRepository getInstance(FriendDataSource dataSource) {
if(instance==null){
instance=new FriendRepository(dataSource);
}
return instance;
}
private FriendRepository(FriendDataSource dataSource){
this.dataSource=dataSource;
}
public Result<List<Friend>> getFriends(String uid){
return dataSource.getFriends(uid);
}
public LiveData<List<FriendRoom>> getFriendLiveData(){
return dataSource.getFriendsLiveData();
}
}
(二)个人主页
1.datasource
public class HomeDataSource {
public Result<User> getUser(){
//访问数据库,并返回信息
APPDataBase appDataBase= App.getInstance().getDataBase();
Single<UserRoom> userSingle=appDataBase.userDao().getLogUser();
try{
UserRoom u=userSingle.blockingGet();
User user=new User(u.uid,u.userName);
return new Result.Success<>(user);
}catch (NullPointerException e){
return new Result.Error(new IOException("Error getUser in",e));
}
}
}
2.repository
public class HomeDataSource {
public Result<User> getUser(){
//访问数据库,并返回信息
APPDataBase appDataBase= App.getInstance().getDataBase();
Single<UserRoom> userSingle=appDataBase.userDao().getLogUser();
try{
UserRoom u=userSingle.blockingGet();
User user=new User(u.uid,u.userName);
return new Result.Success<>(user);
}catch (NullPointerException e){
return new Result.Error(new IOException("Error getUser in",e));
}
}
}
(三)登录
1. datasource
public class LoginDataSource {
public Result<LoggedInUser> login(String uid, String password) {
try {
// 网络登录操作
LoginResponse response=new LoginHpImpl().login(uid,password);
LoggedInUser user=null;
if(response.getCode()>0){
user =
new LoggedInUser(true,
uid,response.getName(),response.getToken()
);
}else {
user =
new LoggedInUser(false,
null,null,null
);
}
return new Result.Success<>(user);
} catch (Exception e) {
return new Result.Error(new IOException("Error logging in", e));
}
}
}
2.respository
public class LoginDataSource {
public Result<LoggedInUser> login(String uid, String password) {
try {
// 网络登录操作
LoginResponse response=new LoginHpImpl().login(uid,password);
LoggedInUser user=null;
if(response.getCode()>0){
user =
new LoggedInUser(true,
uid,response.getName(),response.getToken()
);
}else {
user =
new LoggedInUser(false,
null,null,null
);
}
return new Result.Success<>(user);
} catch (Exception e) {
return new Result.Error(new IOException("Error logging in", e));
}
}
}
二十二、model
public class Friend {
String fid;
String uid;
String f_name;
String nick_name;
public Friend(String fid, String uid, String f_name, String nick_name) {
this.fid = fid;
this.uid = uid;
this.f_name = f_name;
this.nick_name = nick_name;
}
public String getFid() {
return fid;
}
public void setFid(String fid) {
this.fid = fid;
}
public String getUid() {
return uid;
}
public void setUid(String uid) {
this.uid = uid;
}
public String getF_name() {
return f_name;
}
public void setF_name(String f_name) {
this.f_name = f_name;
}
public String getNick_name() {
return nick_name;
}
public void setNick_name(String nick_name) {
this.nick_name = nick_name;
}
@Override
public String toString() {
return "Friend{" +
"fid='" + fid + '\'' +
", uid='" + uid + '\'' +
", f_name='" + f_name + '\'' +
", nick_name='" + nick_name + '\'' +
'}';
}
}
public class LoggedInUser {
private boolean isOk;
private String userId;
private String displayName;
private String token;
public LoggedInUser(boolean isOk, String userId, String displayName, String token) {
this.isOk = isOk;
this.userId = userId;
this.displayName = displayName;
this.token = token;
}
public void setOk(boolean ok) {
isOk = ok;
}
public void setUserId(String userId) {
this.userId = userId;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public LoggedInUser(boolean isOk) {
this.isOk = isOk;
}
public String getUserId() {
return userId;
}
public String getDisplayName() {
return displayName;
}
public boolean isOk() {
return isOk;
}
}
public class Message {
String m_id;
String r_id;
String s_id;
String name;
String time;
String content;
public String getR_id() {
return r_id;
}
public void setR_id(String r_id) {
this.r_id = r_id;
}
public String getS_id() {
return s_id;
}
public void setS_id(String s_id) {
this.s_id = s_id;
}
public String getM_id() {
return m_id;
}
public void setM_id(String m_id) {
this.m_id = m_id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getTime() {
return time;
}
public void setTime(String time) {
this.time = time;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
public class User {
String uid;
String name;
public User(String uid, String name) {
this.uid = uid;
this.name = name;
}
public String getUid() {
return uid;
}
public void setUid(String uid) {
this.uid = uid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class Result<T> {
// hide the private constructor to limit subclass types (Success, Error)
private Result() {
}
@Override
public String toString() {
if (this instanceof Result.Success) {
Result.Success success = (Result.Success) this;
return "Success[data=" + success.getData().toString() + "]";
} else if (this instanceof Result.Error) {
Result.Error error = (Result.Error) this;
return "Error[exception=" + error.getError().toString() + "]";
}
return "";
}
// Success sub-class
public final static class Success<T> extends Result {
private T data;
public Success(T data) {
this.data = data;
}
public T getData() {
return this.data;
}
}
// Error sub-class
public final static class Error extends Result {
private Exception error;
public Error(Exception error) {
this.error = error;
}
public Exception getError() {
return this.error;
}
}
}
AppServer构建
二十三、项目结构
二十四、数据库连接与dao的实现
1.context.xml
第一,在web 目录下创建META-INF目录,在目录下创建文件context.xml
地址构成
jdbc:mysql://用户名@ip:端口/模式名?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8
第二,WEB-INF目录下创建目录lib
加入
<?xml version="1.0" encoding="utf-8"?>
<Context reloadable="true">
<Resource
name="jdbc/minichat"
type="javax.sql.DataSource"
maxTotal="100"
maxIdle="50"
driverClassName="com.mysql.cj.jdbc.Driver"
url="jdbc:mysql://minichat@localhost:3306/mini_chat?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8"
userName="minichat"
password="minichat"
maxWaitMillis="5000"/>
</Context>
2.数据库 Dao
所有的实体dao都应该继承于Dao接口
public interface Dao {
static DataSource getDataSource(){
DataSource dataSource=null;
try{
Context context=new InitialContext();
dataSource=(DataSource)context.lookup("java:comp/env/jdbc/minichat");
}catch (NamingException ne){
ne.printStackTrace();
}
return dataSource;
}
default Connection getConnection() throws DaoException{
DataSource dataSource=getDataSource();
Connection connection=null;
try{
connection=dataSource.getConnection();
}catch (SQLException se){
se.printStackTrace();
}
return connection;
}
}
DaoException
public class DaoException extends Exception implements Serializable {
private static final long serialVersionUID=19192L;
private String message;
public DaoException(){}
public DaoException(String message){
this.message=message;
}
@Override
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
@Override
public String toString() {
return "DaoException{" +
"message='" + message + '\'' +
'}';
}
}
3.Dao
public interface FriendDao extends Dao {
List<FriendResponse> getFriendsById(String uid);
FriendAddResponse addFriend(String uid,String fid);
}
public interface UserDao extends Dao {
boolean signup(String uid,String uname,String password) throws DaoException;
LoginResponse login(String uid,String password);
}
4.DaoImpl
public class FriendDaoImpl implements FriendDao {
@Override
public List<FriendResponse> getFriendsById(String uid) {
List<FriendResponse> friendResponses=new ArrayList<>();
System.out.println(uid);
try(Connection connection=getConnection()){
PreparedStatement pst=connection.prepareStatement("select * from friend where uid=?");
pst.setString(1,uid);
ResultSet rst=pst.executeQuery();
while (rst.next()){
friendResponses.add(new FriendResponse(1,null,rst.getString("fid"),
uid,rst.getString("f_name"),rst.getString("nick_name")));
}
} catch (SQLException sqlException) {
sqlException.printStackTrace();
} catch (DaoException e) {
e.printStackTrace();
}
return friendResponses;
}
@Override
public FriendAddResponse addFriend(String uid, String fid) {
FriendAddResponse response=new FriendAddResponse(-1,null);
try(Connection connection=getConnection()){
String f_name=null;
String uname=null;
PreparedStatement pst=connection.prepareStatement("select uname from user where uid=?");
pst.setString(1,fid);
ResultSet rst=pst.executeQuery();
if(rst.next()){
f_name=rst.getString("uname");
}else {
response.setMessage("id 不存在");
}
pst.setString(1,uid);
rst=pst.executeQuery();
if(rst.next()){
uname=rst.getString("uname");
}else {
response.setMessage("id 不存在");
}
if(f_name!=null&&uname!=null){
PreparedStatement pst2=connection.prepareStatement("insert into friend(fid,uid,f_name) VALUES (?,?,?)");
pst2.setString(1,fid);
pst2.setString(2,uid);
pst2.setString(3,f_name);
int result0=pst2.executeUpdate();
if(result0>0){
pst2.setString(1,uid);
pst2.setString(2,fid);
pst2.setString(3,uname);
int result1=pst2.executeUpdate();
if(result1>0){
response.setCode(1);
response.setMessage("add friend success");
response.setId(fid);
response.setName(f_name);
}else {
response.setMessage("已添加");
}
}else {
response.setMessage("已添加");
}
}
} catch (SQLException | DaoException sqlException) {
sqlException.printStackTrace();
}
return response;
}
}
public class UserDaoImpl implements UserDao {
@Override
public boolean signup(String uid, String uname, String password) throws DaoException{
try(Connection connection=getConnection()){
PreparedStatement pst=connection.prepareStatement("insert into user(uid,uname,password) values (?,?,?)");
pst.setString(1,uid);
pst.setString(2,uname);
pst.setString(3,password);
int result=pst.executeUpdate();
return result>0;
} catch (SQLException sqlException) {
sqlException.printStackTrace();
} catch (DaoException e) {
e.printStackTrace();
}
return false;
}
@Override
public LoginResponse login(String uid, String password) {
System.out.println(uid+password);
LoginResponse loginResponse=new LoginResponse();
loginResponse.setCode(-1);
try(Connection connection=getConnection()){
PreparedStatement pst=connection.prepareStatement("select * from user where uid=?");
pst.setString(1,uid);
ResultSet rst=pst.executeQuery();
while (rst.next()){
if(rst.getString("password").equals(password)){
loginResponse.setCode(1);
loginResponse.setMessage("login ok");
loginResponse.setUid(uid);
loginResponse.setName(rst.getString("uname"));
}else {
loginResponse.setCode(-1);
loginResponse.setMessage("login in password failed");
}
}
}catch (DaoException daoException){
daoException.printStackTrace();
loginResponse.setMessage("login in dao failed");
} catch (SQLException sqlException) {
sqlException.printStackTrace();
loginResponse.setMessage("login in sql failed");
}
return loginResponse;
}
}
5.model
(1)Message
public class Message implements Serializable {
//@param: code 1成功 0未初始化 -1错误
//@param: message 错误信息提示或其他信息
private int code;
private String message;
public Message() {
code=0;
message=null;
}
public Message(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
(2)reuqest
public class FriendAddRequest {
String uid;
String f_id;
public FriendAddRequest(String uid, String f_id) {
this.uid = uid;
this.f_id = f_id;
}
public String getUid() {
return uid;
}
public void setUid(String uid) {
this.uid = uid;
}
public String getF_id() {
return f_id;
}
public void setF_id(String f_id) {
this.f_id = f_id;
}
}
public class FriendRequest {
private String uid;
public FriendRequest(String uid) {
this.uid = uid;
}
public String getUid() {
return uid;
}
public void setUid(String uid) {
this.uid = uid;
}
}
public class LoginRequest {
private String uid;
private String password;
public LoginRequest(String uid,String password) {
this.uid=uid;
this.password=password;
}
public String getUid() {
return uid;
}
public void setUid(String uid) {
this.uid = uid;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
public class SignUpRequest {
String uid;
String uname;
String password;
public SignUpRequest(String uid, String uname, String password) {
this.uid = uid;
this.uname = uname;
this.password = password;
}
public String getUid() {
return uid;
}
public void setUid(String uid) {
this.uid = uid;
}
public String getUname() {
return uname;
}
public void setUname(String uname) {
this.uname = uname;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
(3)response
public class FriendAddResponse extends Message {
String id;
String name;
public FriendAddResponse() {
}
public FriendAddResponse(int code, String message) {
super(code, message);
}
public FriendAddResponse(int code, String message, String id, String name) {
super(code, message);
this.id = id;
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class FriendResponse extends Message {
String fid;
String uid;
String f_name;
String nick_name;
public FriendResponse(int code, String message, String fid, String uid, String f_name, String nick_name) {
super(code, message);
this.fid = fid;
this.uid = uid;
this.f_name = f_name;
this.nick_name = nick_name;
}
public FriendResponse(int code, String message) {
super(code, message);
}
public String getFid() {
return fid;
}
public void setFid(String fid) {
this.fid = fid;
}
public String getUid() {
return uid;
}
public void setUid(String uid) {
this.uid = uid;
}
public String getF_name() {
return f_name;
}
public void setF_name(String f_name) {
this.f_name = f_name;
}
public String getNick_name() {
return nick_name;
}
public void setNick_name(String nick_name) {
this.nick_name = nick_name;
}
}
public class LoginResponse extends Message {
private String uid;
private String name;
public LoginResponse() {
}
public LoginResponse(int code, String message) {
super(code, message);
}
public LoginResponse(int code, String message, String uid, String name) {
super(code, message);
this.uid = uid;
this.name = name;
}
public String getUid() {
return uid;
}
public void setUid(String uid) {
this.uid = uid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "LoginResponse{" +
"uid='" + uid + '\'' +
", name='" + name + '\'' +
'}';
}
}
二十五、servelet
@WebServlet(name = "FriendsGetServlet",urlPatterns = {"/getFriends"})
public class FriendsGetServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
FriendRequest friendRequest=new Gson().fromJson(request.getReader(),FriendRequest.class);
List<FriendResponse> friendResponseList=new FriendDaoImpl().getFriendsById(friendRequest.getUid());
Type listType=new TypeToken<List<FriendResponse>>(){}.getType();
String rp=new Gson().toJson(friendResponseList,listType);
response.setContentType("txt/html;charset=utf-8");
PrintWriter writer=response.getWriter();
writer.print(rp);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String uid=request.getParameter("uid");
List<FriendResponse> friendResponseList=new FriendDaoImpl().getFriendsById(uid);
Type listType=new TypeToken<List<FriendResponse>>(){}.getType();
String rp=new Gson().toJson(friendResponseList,listType);
response.setContentType("txt/html;charset=utf-8");
PrintWriter writer=response.getWriter();
writer.print(rp);
}
}
@WebServlet(name = "FriendAddServlet",urlPatterns ={"/addFriend"})
public class FriendAddServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
FriendAddRequest friendAddRequest=new Gson().fromJson(request.getReader(),FriendAddRequest.class);
FriendAddResponse friendAddResponse=new FriendDaoImpl().addFriend(friendAddRequest.getUid(),friendAddRequest.getF_id());
String rp=new Gson().toJson(friendAddResponse);
response.setContentType("txt/html;charset=utf-8");
PrintWriter writer=response.getWriter();
writer.print(rp);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}
@WebServlet(name = "LoginServlet",urlPatterns = {"/login"})
public class LoginServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
LoginRequest loginRequest=new Gson().fromJson(request.getReader(),LoginRequest.class);
LoginResponse loginResponse=new UserDaoImpl().login(loginRequest.getUid(),loginRequest.getPassword());
System.out.println(loginRequest.getUid()+" ask for login {password:"+loginRequest.getPassword()+"}");
System.out.println(loginResponse.getUid()+" code:"+loginResponse.getCode());
String rp=new Gson().toJson(loginResponse);
response.setContentType("txt/html;charset=utf-8");
PrintWriter writer=response.getWriter();
writer.print(rp);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}
@WebServlet(name = "SignUpServlet",urlPatterns = {"/signup"})
public class SignUpServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("signupservlet start");
SignUpRequest signUpRequest=new Gson().fromJson(request.getReader(),SignUpRequest.class);
Message message=new Message();
try{
boolean isOk=new UserDaoImpl().signup(signUpRequest.getUid(),signUpRequest.getUname(),signUpRequest.getPassword());
if(isOk){
message.setCode(1);
message.setMessage("sign up success");
}else {
message.setCode(-1);
message.setMessage("sign up failed");
}
}catch (DaoException e){
message.setCode(-1);
message.setMessage("sign up failed");
}
String rp=new Gson().toJson(message);
System.out.println(rp);
response.setContentType("txt/html;charset=utf-8");
PrintWriter writer=response.getWriter();
writer.print(rp);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}