中山大学数据科学与计算机学院本科生实验报告
(2019年春季学期)
课程名称 | IOS开发 | 任课老师 | 郑贵锋 |
年级 | 16 | 专业(方向) | 软件工程(计算机应用方向) |
学号 | 16340132 | 姓名 | 梁颖霖 |
电话 | 13680473185 | Email | dic0k@qq.com |
开始日期 | 2019/5/12 | 完成日期 | 2019/5/15 |
一、实验题目
IM聊天工具
二、实现内容
- 个人详情页面UI
- 后端接口:获取聊天消息记录
三、实验结果
聊天消息记录
1.概述
为了实现聊天工具对话的消息传递,这里我们引入了序列号的概念,每一个消息都有它特定的序号,我们根据这个序列号来确定这个消息是否已经发送,是否已经被接受到,是否需要删除等逻辑。
除此之外,为了实现多用户间的对话,例如A->B, A->C, B->C,他们之间如何能找到属于他们各自的信息呢?这里我们利用的是维护一个消息表,每一条消息都有发送用户的id,接受用户的id,内容的id,然后再通过内容的id在内容表格中找具体的内容。
2. URL设计
获取用户之间聊天记录
/history/personal
POST
{
to: {username}
data: {}
}
在Django的urls.py文件中表现为
urlpatterns = [
path('personal', views.personal, name='personal'),
]
3. 具体实现
a.从消息表通过用户名获取聊天记录
# 返回聊天记录
def personal(request, t_username):
response = {'state':'fail', 'msg':'no msg'}
# 要在登录状态下
if 'login_id' not in request.session:
response['msg'] = 'no login'
return HttpResponse(json.dumps(response), content_type = 'application/json')
# 只允许GET方法
if request.method != 'POST':
response['msg'] = 'wrong method'
return HttpResponse(json.dumps(response), content_type = 'application/json')
# 查询的用户名为空
if t_username == '':
response['msg'] = 'miss username'
return HttpResponse(json.dumps(response), content_type = 'application/json')
# 已经登录, 所以拿取用户信息
cur_username = request.session['login_id']
# 数据库操作,查询消息
try:
# 当前用户发送给对方的信息
cur_to_t_msg = Msg.objects.filter(Username = t_username, From = cur_username)
# 对方发送给当前用户的信息
t_to_cur_msg = Msg.objects.filter(Username = cur_username, From = t_username)
except Exception as e:
response['msg'] = 'db error'
return HttpResponse(json.dumps(response), content_type = 'application/json')
else:
# 根据两类消息的ContentID来找出所有content
# 这里先假设所有记录为文字类型,图片类型未知结果,待测试
# Content_Text
ctArray = []
# Content_Image
ciArray = []
for msg in cur_to_t_msg:
if msg.Type == 'text':
ctArray.append(Content_Text.objects.filter(Cid = msg.ContentID))
elif msg.Type == 'image':
ciArray.append(Content_Image.objects.filter(Cid = msg.ContentID))
for msg in t_to_cur_msg:
if msg.Type == 'text':
ctArray.append(Content_Text.objects.filter(Cid = msg.ContentID))
elif msg.Type == 'image':
ciArray.append(Content_Image.objects.filter(Cid = msg.ContentID))
# 根据ContentID来进行append,保证时间有序
if len(Content_Text) >= 1:
# 序列化,返回多条文字内容
serialized_obj = serializers.serialize('json', ctArray)
response = {'state':'ok', 'msg':'ok', "data":serialized_obj}
else:
response['msg'] = 'no data'
return HttpResponse(json.dumps(response), content_type = 'application/json')
具体逻辑
- 定义返回体的内容,包括一个state和msg
- 判断是否已经登陆状态
- 是,直接返回HttpResponse,告诉前端已经登陆,无须注册
- 否,下一步
- 判断方法是否为GET
- 判断查询的用户名是否为空
- 获取用户登陆的用户名以及密码
- 数据库操作
- 通过用户名获取当前用户发送给对方的信息
- 通过用户名获取对方发送给当前用户的信息
- 根据两类消息的ContentID来找出所有content
- 根据ContentID来进行append,保证时间有序
- 序列化,返回多条文字内容
个人详情页面
效果截图:
个人详情页面是使用UITableView来模仿微信聊天工具的详情所制作的
InfoViewController.h
#import <UIKit/UIKit.h>
#import "UserModel.h"
NS_ASSUME_NONNULL_BEGIN
@interface InfoViewController : UIViewController
@property (weak, nonatomic) IBOutlet UILabel *Birthplace;
@property (weak, nonatomic) IBOutlet UILabel *NickName;
@property (weak, nonatomic) IBOutlet UILabel *ID;
@property (weak, nonatomic) IBOutlet UILabel *Gender;
@property (strong, nonatomic) UserModel* User;
@property (weak, nonatomic) IBOutlet UIImageView *ProfilePicture;
@end
首先在.h文件定义所用到的一些属性,包括用户名,地区,性别,id号,头像等,还有一个登陆后的User的信息。
InfoViewController.m
这里首先定义所用到TableView,以及左侧的标题列表,右侧的内容列表来存储数据
#import "InfoViewController.h"
@interface InfoViewController ()<UITableViewDelegate, UITableViewDataSource>
@property(nonatomic, strong) UITableView *tableView;
@property(nonatomic, strong) NSMutableArray<NSString*> *titleList;
@property(nonatomic, strong) NSMutableArray<NSString*> *contentList;
@end
viewDidLoad函数中加载数据
- 绑定User中的信息到之前定义的属性当中。
- 定义navigationItem的标题名。
- 获取屏幕的宽与高来定义tableview视图的大小与位置
- 取消tableview默认的多余的横线
- (void)viewDidLoad {
[super viewDidLoad];
// [self.navigationController setNavigationBarHidden:NO animated:NO];
self.navigationController.navigationBarHidden = NO;
// Do any additional setup after loading the view.
if (self.User == nil)
{
self.User = [[UserModel alloc] initWithProperties:@"peppa ID" NickName:@"Peppa" RemarkName:@"peppy" Gender:@"female" Birthplace:@"UK" ProfilePicture:@"peppa.jpg"];
}
self.ProfilePicture.image = [UIImage imageNamed:self.User.ProfilePicture];
self.NickName.text = self.User.NickName;
self.ID.text = self.User.UserID;
self.Gender.text = self.User.Gender;
self.Birthplace.text = self.User.Birthplace;
self.navigationItem.title = @"个人信息";
// 获取屏幕的宽高
CGRect rect = [[UIScreen mainScreen] bounds];
CGSize size = rect.size;
CGFloat width = size.width;
CGFloat height = size.height;
self.tableView = ({
UITableView *tableView = [[UITableView alloc]
initWithFrame:CGRectMake(0, 50, width, height/2+70) style:UITableViewStylePlain];
tableView.delegate = self;
tableView.dataSource = self;
tableView;
});
// 取消多余的横线
self.tableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero];
[self.view addSubview:self.tableView];
[self loadData];
}
加载数据,定义titleList,contentList
- (void)loadData {
self.titleList = [NSMutableArray array];
self.contentList = [NSMutableArray array];
[self.titleList addObjectsFromArray:[[NSArray alloc] initWithObjects:@"头像", @"昵称", @"账号", @"性别", @"地区",nil]];
[self.contentList addObjectsFromArray:[[NSArray alloc] initWithObjects:@"小猪佩奇", @"Peppa", @"peppy", @"female", @"UK",nil]];
}
- 用numberOfSectionsInTableView定义tableview的section数目
- 用heightForRowAtIndexPath定义tableview每一个cell的高度
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.titleList.count;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 80;
}
根据不同的行来给每个cell自定义accessoryView, 并绑定不同的数据。这里我们第一行是图片,故需要特殊处理来显示图片,而其他行则是显示内容,我这里修改一下它的字体与大小使得更加美观,对比度更加高。
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *cellID = [NSString stringWithFormat:@"cellID:%zd", indexPath.section];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID];
if (nil == cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellID];
}
// cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.textLabel.text = self.titleList[indexPath.row];
cell.textLabel.font = [UIFont fontWithName:@"PingFangSC-Regular" size:18.f];
if (indexPath.row != 0){
UILabel *rightLabel = [[UILabel alloc]initWithFrame:CGRectMake(0,0,70,55)];
rightLabel.text = self.contentList[indexPath.row];
rightLabel.font = [UIFont fontWithName:@"PingFangSC-Regular" size:14.f];
rightLabel.textColor = [UIColor grayColor];
cell.accessoryView = rightLabel;
//cell.accessoryView.backgroundColor = [UIColor redColor]; //加上红色容易看清楚
}
else{
cell.accessoryView = ({
UIImageView *imgV = [[UIImageView alloc] initWithImage:[UIImage imageNamed:self.User.ProfilePicture]];
CGRect frame = imgV.frame;
frame = CGRectMake(0, 0, 100, 55);
imgV.frame = frame;
[imgV setContentMode:UIViewContentModeScaleAspectFit];
imgV;
});
}
return cell;
}
点击列表的item时跳转至修改页面
#pragma mark ------------ UITableViewDelegate ------------------
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
InfoViewController *controller = [[InfoViewController alloc] init];
controller.hidesBottomBarWhenPushed = YES;
[self.navigationController pushViewController:controller animated:YES];
}
用户退出按钮绑定事件,这里要与服务器进行交互,删除之前登陆的session
- (IBAction)logout:(id)sender {
NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:defaultConfigObject delegate:nil delegateQueue:[NSOperationQueue mainQueue]];
NSURL *url = [NSURL URLWithString:@"http://118.89.65.154:8000/account/logout/"];
NSURLSessionDataTask *task = [session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if(error == nil) {
if(NSClassFromString(@"NSJSONSerialization")) {
NSError *e = nil;
id object = [NSJSONSerialization JSONObjectWithData:data options:0 error:&e];
if(e) {
NSLog(@"error");
}
if([object isKindOfClass:[NSDictionary class]]) {
NSDictionary *result = object;
if([result[@"state"] isEqualToString:@"ok"]) {
NSLog(@"logout success");
UIStoryboard *indexStoryboard = [UIStoryboard storyboardWithName:@"Index" bundle:nil];
[UIApplication sharedApplication].keyWindow.rootViewController = indexStoryboard.instantiateInitialViewController;
}
else {
NSLog(@"logout fail");
}
}
else {
NSLog(@"Not dictionary");
}
}
}
else {
NSLog(@"网络异常");
}
}];
[task resume];
}