成果展示
该项目基于开源的飞机大战小程序,构建了登陆验证机制,主要是完成了在鸿蒙中的网络通信和前后端交互功能,可以实现注册用户并登陆,用户游戏记录保存和展示功能,项目源码在此,部分运行成果如图:
构建思路
飞机大战应该很容易找到开源的素材,拿来修修改改应该是最快的, 但这样就成了只写前端的脚本小子,所以目前的想法是通过登陆界面,实现后端保存记录的功能,打通前后端交互这条路。目前计划的流程图如下:
看起来也很简单,但是很多操作以前根本没有实践过,不能以现在的视角嘲笑过去幼稚的自己,通过这个比较简单的例子,真正打通前后端,又是基于鸿蒙体系,对我来说也是一次比较大的实践了。
前端——登陆界面
这里设置三个界面,分别开放登陆、注册和忘记密码,登陆入口主要实现不同页面的跳转,通过注册字符和按钮的点击事件跳转到响应界面,并给予响应提示。
在所有任务之前,因为项目需要网络通信,所有首先把网络权限赋给该项目,在module.json5
的module
中增加如下代码
requestPermissions:[
{
name: "ohos.permission.INTERNET"
},]
图标配置
应用生成后默认是一个名为lable
有四个方框的图标,我们可以在src\main\module.json5
中修改配置文件实现图标自定义,在其中abilities
模块中分别修改icon
和label
字段,按住ctrl
步入字符常量,
图标和标签分别修改如下:
修改后的结果展示如图:
登陆入口
常规登陆入口由一个图标,用户名和密码输入栏,以及登陆按钮组成,完成后的界面如下:
该部分可用列排列,分别包含图片,两个输入框,登陆按钮和注册字符,其中注册字符是横向排列的两个字符串,所以可以额外定义一个行排列,大致框架代码如下:
import { router } from '@kit.ArkUI';
import {req} from "./http"
@Entry
@Component
struct Index {
@State userName:string="admin"
@State passWord:string="";
build() {
//列边距20
Column({space: 20}) {
Image($r("app.media.huawei_logo")).width('30%')
// 顶部距离
.margin({top: '300px'})
Text("欢迎登陆")
TextInput({
// 设置默认登陆用户名
placeholder:"请输入用户名",text:this.userName
})//输入框内的值给用户名
.onChange((value) =>{
this.userName=value
})
TextInput({
placeholder:"请输入密码"
}).type(InputType.Password)
//设置密码格式
.onChange((value) =>{
this.passWord=value
})
Button("登陆").width('75%')
.onClick(() => {
//登陆事件代码
}
)
Row({space: 20}){
Text("注册").fontSize(15)
.onClick(() => {
//点击事件跳转路由,到注册界面
router.pushUrl({
url:"pages/Sigin"
})
})
Text("忘记密码").fontSize(15)
.onClick(() => {
//点击事件跳转路由,到修改密码界面
router.pushUrl({
url:"pages/ForgetPassport"
})
})
}
}//默认具中,设置宽度即可实现
.width('100%')
//内边距
.padding(20);
}
}
登陆验证——同步处理
该部分需要构造网络请求发送给服务器,可以通过自带的http
模块实现,大概借助如下方法即可:
// 首先声明该方法并不能达成效果!!!
// 实例化http请求对象
let httpRequest = http.createHttp();
// 构造http请求
httpRequest.request(
// 填写http请求的url地址,
url,
{
method: http.RequestMethod.POST, // 可选,默认为http.RequestMethod.GET
// 当使用POST请求时此字段用于传递内容
header: {
'Content-Type': 'application/json',
},
extraData: {
"request_type":type,
"username": username,
"password": password,
},
},
(err, data) => {
if (!err) {
// 处理正常逻辑
}
else{
// 异常处理
}
}
)
}
但这里算是我在项目中踩了最大的一个坑,卡了我几乎一天,就是同步问题,问题表现为在按钮的onclick
事件调用该方法并不能获得任何信息,对函数和按钮事件分别进行输出后发现,函数的输出晚于按钮事件的输出,这说明函数体反而是后执行完毕的。
这里我去网上查了资料,说request
请求是异步的,也就是说执行到请求后程序会直接往下执行,而不等请求信息,出发点是避免网络拥堵造成程序等待,但很多时候我们的信息是有上下文依赖的,这种情况下我们需要手动设置,使程序等待请求结果。
在该阶段我分别尝试了弹窗 AlertDialog.show({ message:"密码或用户名错误,请重新输入" })
等待,设置延迟setTimeout
方法,发现都不好用,这些电脑端编程的方法都不好用,移动端似乎异步为主,所以只能学习正规的同步手段。
以嵌套读取新闻等背景大致介绍几种方法,比如读取网页标签,进入不同网页后读取分页面,最终读取文本信息的三层嵌套。
该部分的讲解在此,这里只是做简单记录。
传统回调——STEP ONE
每次读取的结果res
中写入下一步处理方法,如
load((url){
success(res=>{
// 请求成功获取id
let id = get(data[0].id)
request(url){
// 新请求的内容获取id
data:aid=id
request(url){
}
}
})
}
)
即每次请求成功的处理函数中连接新的请求,该方法可读性很差,不易维护,最好能将每次处理独立出来,使得结构更清晰,避免将所有处理写在一起,由此产生了第二种方法——回调函数。
回调函数——STEP TWO
基于传统调用的问题,有了将处理写为回调函数的方法,即在函数参数中引入函数,最后的结果用参数函数处理,具体代码如下:
load(){
this.request1(res=>{
id=res.data[0].id
this.request2(id,res=>{
aid=res.data[0].id
})
})
}
request1((callback){
success(res=>{
// 请求成功获取id
callback(res)
})
}
request2((id,callback){
success(res=>{
// 请求成功获取id
data:cid=id
callback(res)
})
}
)
该方法下的代码相对易懂易维护,主函数中只有处理数据的代码而没有具体步骤,需要修改时查阅具体函数即可,但其本质上还是嵌套调用,查询次数多时代码很冗余,在此基础上更改进的版本为promise
。
Promise方法——STEP THREE
借助一个promise
对象跳出循环嵌套,该对象有三种状态,分别为pending
、resolved
和rejected
,本身有then
和catch
方法,其中resolve
和reject
分别对应处理成功和失败两种方法,then
表示处理后对象的返回值,在此基础上用promise
方法重构代码如下:
init(){
this.request1().then(res=>{
id=res[0].id
// 返回request有id参数的处理后的promise对象
retrun request2(id)
})
.then(res=>{
})
}
request1(){
// 直接返回promise对象
return new Promise((resolve,reject)=>{
request(url)
success:res=>{
// 对象返回res信息,状态切换为resolved
resolve(res)
}
}fail:err{
// 返回err信息,状态切换为rejected
reject(err)
}
}
request2(id){
return new Promise((resolve,reject)=>{
request(url,id)
success:res=>{
// 对象返回res信息,状态切换为resolved
resolve(res)
}
}fail:err{
// 返回err信息,状态切换为rejected
reject(err)
}
}
使用该方法可以将原有嵌套调用的方法转为链式调用,即不断生成新的promise
对象,并不断处理。
异步处理同步化async/await——STEP FOUR
使用两个关键字async
和await
分别修饰函数和promise
方法,即可实现同步执行,后续代码会等待await
执行完毕后再继续执行,实现函数同步执行,加载函数直接修改为:
asyc load(){
res = await request1()
id=res[0].id
res = await request2(id)
}
基于promise的修改版
当时只学到promise
,所以目前的代码只是第三版,http
请求方法的最终方案为:
import http from '@ohos.net.http';
//request为异步方法,不等待回调函数执行结束,需要手动设置
export async function req(url:string, username:string, password:string,type:number):Promise<string> {
return await new Promise((resolve, reject) => {
let httpRequest = http.createHttp();
httpRequest.request(
// 填写http请求的url地址,
url,
{
method: http.RequestMethod.POST, // 可选,默认为http.RequestMethod.GET
// 当使用POST请求时此字段用于传递内容
header: {
'Content-Type': 'application/json',
},
extraData: {
"request_type":type,
"username": username,
"password": password,
},
},
(err, data) => {
if (!err) {
// data.result为http响应内容
console.info('Result:' + data.result);
console.info('code:' + data.responseCode);
resolve(data.result.toString())
} else {
console.info('error:' + JSON.stringify(err));
// 当该请求使用完毕时,调用destroy方法主动销毁。
httpRequest.destroy();
reject(data.result)
}
}
)
})
}
登陆按键绑定事件为,发送账号密码给服务器,接收返回的报文,如果标识为pass
或不为reject
则验证通过跳转页面,否则提示错误信息。
Button("登陆").width('75%')
.onClick(() => {
console.log("正在验证");
// 发送账号密码给服务器
req( this.url, this.userName, this.passWord,1).then(res=>{
console.log("包含信息为:"+res)
if (res !="reject") {
// 登陆时获取用户最高成绩记录
this.record=res
console.log("验证通过")
// 验证通过进入下一页面
router.pushUrl({
url: "pages/Plane_Game",
// 传递信息
params:{
username:this.userName,
record:this.record
}
})
console.log("传递用户名给下一页:"+this.userName)
}
else{
AlertDialog.show({
message:"密码或用户名错误,请重新输入"
})
}
}).toString()
}
注册界面
该部分与登陆界面基本类似,不同的就是密码需要输两遍,像市面上的产品一样,防止用户第一次把密码打错了后面登不上去,两次输入结果如果不同的话就直接弹窗报错,如果一致的话,点击按钮再把用户名和密码发送到后端,让其进行持久化。
逻辑基本类似,但为了记录,直接展示其页面源代码:
import {req} from "./http"
import { router } from '@kit.ArkUI';
@Entry
@Component
struct Sign {
@State userName:string=""
@State passWord:string=""
@State SecondPassWord:string=""
url: string = "http://127.0.0.1:1111/"
build() {
Column({space: 20}) {
Image($r("app.media.huawei_logo")).width('30%')
// 顶部距离
.margin({top: '300px'})
Text("欢迎注册")
TextInput({
placeholder:"请输入用户名"
})
.onChange((value) =>{
this.userName=value
})
TextInput({
placeholder:"请输入密码"
})//设置密码格式
.type(InputType.Password)
.onChange((value) =>{
this.passWord=value
})
TextInput({
placeholder:"请确认密码"
})//设置密码格式
.type(InputType.Password)
.onChange((value) =>{
this.SecondPassWord=value
})
Button("确认注册").width('75%')
.onClick(()=>{
//确认密码不相同
if(this.passWord!=this.SecondPassWord){
console.log(this.passWord+" 两次密码分别为"+this.SecondPassWord)
AlertDialog.show({
message:"两次密码不相同,请重新输入"
})
}
else{
//发送账号密码给服务器
req(this.url,this.userName,this.passWord,2).then(res=> {
console.log("注册获得返回信息:" + res);
if (res === "pass") {
console.log("验证通过")
// 验证通过进入下一页面
router.pushUrl({
url: "pages/Index"
})
} else {
AlertDialog.show({
message: "发生错误"
})
}
})
}
})
}//默认具中,设置宽度即可实现
.width('100%')
//内边距
.padding(20);
}
}
形成的界面如下:
前端——飞机大战
该部分为找的开源项目,其他类似实现思路也有借助画布的,但因为没有代码,故最终采取了现在的版本。
该项目使用重复刷新初始化的方法,即每次逻辑运算产生飞机、子弹、敌机,和各种状态判断等信息,通过不断初始化产生动画效果,ai生成的控制流程图如下:
flowchart TD
Start[初始化] --> A[设置屏幕宽高、刷新周期等]
A --> B[初始化玩家飞机、敌机、子弹等状态]
B --> C[启动定时器]
C --> D{是否游戏结束}
D -->|是| E[显示得分和重新开始按钮]
E --> F[等待用户点击重新开始]
F --> G[重新初始化游戏]
G --> C
D -->|否| H[更新帧数]
H --> I{帧数 % 50 == 0}
I -->|是| J[生成敌机]
J --> K[更新敌机位置]
I --> K
K --> L{帧数 % 5 == 0}
L -->|是| M[生成子弹]
M --> N[更新子弹位置]
L --> N
N --> O[检测子弹与敌机碰撞]
O --> P[检测玩家飞机与敌机碰撞]
P --> Q[检测屏幕外的敌机和子弹]
Q --> R[回收屏幕外的敌机和子弹]
R --> S[帧数加1]
S --> C
项目整体采用stack
堆叠布局,这里只解读一些关键代码
触摸移动
.onTouch((event: TouchEvent) => {
if (event.type === TouchType.Move) {
this.myPlane.x = event.touches[0].x
this.myPlane.y = event.touches[0].y
}
})
堆叠布局的触摸事件代码,在拖动事件中绑定,飞机的坐标为当前触摸坐标,就是有时候会瞬移。
歼敌判定
checkCollision() {
// 修改 this.bullets 和 this.enemyPlanes 数组,以及 this.score 变量
// 存储发生碰撞的子弹的索引
let bulletIndexes: number[] = [];
// 存储发生碰撞的敌机的索引
let enemyPlaneIndexes: number[] = [];
for (let i = 0; i < this.bullets.length; i++) {
let bullet = this.bullets[i]
for (let j = 0; j < this.enemyPlanes.length; j++) {
// 使用 bullet.isCollisionWith(this.enemyPlanes[j]) 方法检查当前子弹是否与当前敌机发生碰撞
if (bullet.isCollisionWith(this.enemyPlanes[j])) {
bulletIndexes.push(i)
// 未添加到碰撞敌机数组,则加分
if (!enemyPlaneIndexes.includes(j)) {
enemyPlaneIndexes.push(j)
this.score++
}
break
}
}
this.returnToPool(this.bullets, bulletIndexes)
this.returnToPool(this.enemyPlanes, enemyPlaneIndexes)
}
}
该部分用于处理子弹和敌机的碰撞,通过遍历子弹来判断子弹和飞机体积的位置是否有重合,重合说明击中敌机,就增加分数,将子弹和敌机分别回收到对象池中。
死亡判定也类似,判定自己的飞机是否与敌机碰撞。
离屏回收
该项目其实有一定优化思想,并不是无止境地生成敌机和子弹,而是屏幕中生成固定数量的飞机和子弹,离屏后自动销毁回收,详细代码如下:
checkOffScreenSprites(sprites: Sprite[]) {
// 定义离屏数组
let offScreenIndexes: number[] = [];
for (let i = 0; i < sprites.length; i++) {
let sprite = sprites[i]
// 超出屏幕范围的加入离屏数组
if (sprite.isOffScreen(this.screenWidth, this.screenHeight)) {
offScreenIndexes.push(i)
}
}
// 离屏范围数组的对象返回池中
this.returnToPool(sprites, offScreenIndexes)
}
其中isOffScreen
很简单,只要判断当前坐标与屏幕大小即可,其余逻辑为:
遍历检查所有对象,屏幕外的用索引记录,用于后续回收使用更新信息。
初始化界面
init() {
this.frames = 0
this.enemyPlanes = []
this.bullets = []
this.isGameOver = false
this.initMyPlane()
this.spriteManager.init(this.screenWidth, this.screenHeight)
let _this = this
this.pageRefreshIntervalId = setInterval(() => {
if (_this.frames % 50 == 0) {
_this.createEnemyPlane();
}
if (_this.frames % 5 == 0) {
_this.createBullet(_this.myPlane.x, _this.myPlane.y - _this.myPlane.height / 2);
}
_this.enemyPlanes.forEach((item) => {
item.update();
});
_this.bullets.forEach((item) => {
item.update();
});
_this.checkCollision();
_this.checkMyPlaneCollision();
_this.checkOffScreenSprites(_this.bullets);
_this.checkOffScreenSprites(_this.enemyPlanes);
_this.frames++;
}, this.pageRefreshInterval)
}
初始化页面函数,每50帧生成一个敌机,5帧生成一颗子弹,遍历二者数组,更新位置,随后检测碰撞与屏幕外子弹和飞机,最后帧数自增。
主程序
build() {
Stack({ alignContent: Alignment.TopStart }) {
Image($r('app.media.bg')).width('100%').height('100%')
if (this.isGameOver) {
Column() {
Button({ type: ButtonType.Capsule, stateEffect: true }) {
Text('重新开始').fontSize(30).fontColor(0xffffff).padding({ left: 30, right: 30, top: 10, bottom: 10 })
}
.onClick((event: ClickEvent) => {
this.init()
})
Text("得分: " + this.score).margin({ top: 10, left: 20 }).fontSize(20)
}.justifyContent(FlexAlign.Center).width('100%').height('100%')
} else {
Image($r('app.media.my_plane'))
.width(this.myPlane.width).height(this.myPlane.height)
.margin({ top: this.myPlane.y - this.myPlane.height / 2 + 10, left: this.myPlane.x - this.myPlane.width / 2 })
ForEach(this.enemyPlanes, (item:Sprite) => {
Image($r('app.media.enemy_plane'))
.width(item.width).height(item.height)
.margin({ top: item.y, left: item.x })
})
ForEach(this.bullets, (item:Sprite) => {
Image($r('app.media.bullet'))
.width(item.width).height(item.height)
.margin({ top: item.y, left: item.x })
})
}
}.width('100%').height('100%')
根据游戏状态判定是否结束,是则弹出按钮和游戏记录,按钮的点击事件为初始化函数,否则一次加载,分别显示飞机,敌机和子弹。
帧数不断增加会不会导致溢出呢?
其实这里应该加一步定时清零操作。
回传记录
该部分负责将本次游戏记录回传给服务端,用得分作为记录,安排在重新开始按钮的触发函数中,当本次记录大于登陆页面传来的最高记录才更新,代码如下:
.onClick((event: ClickEvent) => {
console.log("获取用户名为:"+this.username);
// 发送记录给后台,更新数据
if(this.username){
// 判定,username不为空才执行
if(this.score>Number(this.record)){
req(this.url, this.username, this.score.toString(),3)
console.log("更新记录成功");
}else{
console.log("未更新记录");
}
}
后端——数据处理
因为之前学过Java连接数据库,这是很基础但是很重要的一步,而且也听说Java对前后端的处理很完善,所以该项目继续使用Java作为完成的工具。
数据库连接
参照JDBC连接数据库文件即可,分别完成注册驱动,获取数据库连接对象,获取数据库操作对象,执行sql语句,获取结果集,回收资源,在项目中直接返回操作对象的代码整合为一个函数,展示如下:
public Statement build_connection() {
try {
// 注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
try{
// 获取数据库连接对象
Connection connection= DriverManager.getConnection("jdbc:mysql://localhost:3306/InfoForUser?characterEncoding=utf8&serverTimezone=GMT%2B8"
,"root",null);
connection.setAutoCommit(false);
// 获取数据库操作对象
Statement statement=connection.createStatement();
System.out.println("获取对象成功");
return statement;
}
catch (SQLException e) {
throw new RuntimeException(e);
}
}
数据处理
该部分负责把前端传来的用户名和密码在数据库中查询,返回查询结果,或在数据库中添加数据,定义为三个函数,分别接收数据库操作对象statement
、username
和password
,区别是函数体中执行sql
语句不同,
1,登陆检验功能是将数据在数据库中检索,检索得到说明口令合法,返回ture
。
2,新增数据是直接在数据库中insert
一条记录。
3,更新记录负责对用户的record
字段进行更新。该步逻辑稍微复杂,数据库不是很扎实的可以分两步走,首先获得用户记录,再进行比较,如果新值较大则更新。
具体展示如下:
//数据库检查用户名密码是否正确
public boolean check_user(Statement statement,String user, String password) {
try {
ResultSet resultSet = statement.executeQuery("select * from UserInfo where username='" + user + "' and password='" + password + "';");
if (resultSet.next()) {
System.out.println("用户名密码正确");
return true;
} else {
System.out.println("用户名密码错误");
return false;
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
// 新增用户
public boolean add_user(Statement statement,String user, String password) {
try {
statement.executeUpdate("insert into UserInfo values('" + user + "','" + password + "');");
return true;
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
// 获取用户记录
public String get_userRecord(Statement statement,String user) {
try {
ResultSet resultSet = statement.executeQuery("select record from userinfo where username='" + user + "';");
if (resultSet.next()) {
return resultSet.getString("record");
} else {
System.out.println("用户不存在");
return null;
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
// 更新用户成绩
public boolean update_userRecord(Statement statement,String user, String record) {
try {
statement.executeUpdate("update userinfo set record='" + record + "' where username='" + user + "';");
return true;
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
网络连接
该部分需要Java负责完成针对特定网络端口的监听,接收报文并响应,目前的逻辑是用一个缓冲区接收所有信息,然后在函数中根据字符串查找,首先获取用户名和密码,然后判断请求类型,是登陆还是注册,登陆就在数据库中查询是否有该记录,有就返回pass
允许登陆,否则reject
,注册请求也类似,新增记录到数据库中,成功返回pass
,否则返回reject
。
该模块大致流程如下:
监听模块代码如下:
//创建socket,绑定监听端口1111
ServerSocket serverSocket = new ServerSocket(1111);
System.out.println("Server started on port 1111");
while (true) {
//循环监听等待请求
Socket socket = serverSocket.accept();
System.out.println("New client connected");
byte[] buffer =new byte[1024];
int a = socket.getInputStream().read(buffer);
//响应对象
OutputStream resp = socket.getOutputStream();
//报文转化成字符串
String requestLine = new String(buffer, 0, a);
System.out.println("报文内容为:"+requestLine);
}
一次通信的报文信息展示如下:
而更新部分并不强制要求响应,故延续即可,无需规定。
报文解析
本来是有现成的框架比如Spring
、tomcat
等等一大堆,但是因为现在写的只算是一个小demo,暂时没必要学太深,所以只从字符串处理的角度来完成。
该部分需要三个方法,分别解析post
消息体中的请求类型、用户名和密码,因为psot消息体大多使用JSON
格式,键值对对应数据的格式,所以在字符串中,我们只需要找到其键值位置,往后加几位读取即可,比如indexOf("password")
即可找到password
关键字的第一个位置,然后设置start
的值为indexof
+11跳过其中的字段及报文格式信息,end
结束字段为报文末尾,去掉格式两个字符,即length()-2
,最后通过String password = requestLine.substring(start, end);
截取报文内容即可获得密码。
类似的,三个字段的获取代码如下:
//获取请求类型
public static String getPosttype(String requestLine) {
//加14位因为还有request_type":
int start =requestLine.indexOf("request_type")+14;
System.out.println("开始位置为:"+start);
//只读一位即可
int end =start+1;
System.out.println("结束位置为:"+end);
String posttype = requestLine.substring(start, end);
System.out.println("获取到的post类型为:" + posttype);
return posttype;
}
//获取用户名
public static String getUsername(String requestLine) {
//加11位因为还有“和:
int start =requestLine.indexOf("username")+11;
System.out.println("开始位置为:"+start);
//减一位因为还有,
int end =requestLine.indexOf("password")-3;
System.out.println("结束位置为:"+end);
String username = requestLine.substring(start, end);
System.out.println("获取到的用户名为:" + username);
return username;
}
//获取密码
public static String getPassword(String requestLine) {
//加3位因为":"
int start =requestLine.indexOf("password")+11;
//减2位因为还有“}
int end =requestLine.length()-2;
String password = requestLine.substring(start, end);
System.out.println("获取到的密码为:" + password);
return password;
}
其中获取密码方法可以直接用于获取记录,只需要服务端发包时将记录放在密码参数中即可。
报文响应
接收消息并能识别请求信息后,要根据请求内容对其进行响应,同样返回一个http
报文,该步骤借助OutputStream
输出流实现,输入输出流基于socket
套接字,自动绑定端口进行监听回应,构造构造消息后调用输出流的wirte
方法即可实现报文响应,具体代码如下:
public static boolean Response(OutputStream resp,String body){
StringBuilder response = new StringBuilder();
// 构造响应消息头
response.append("HTTP/1.1 ").append(200).append("\r\n")
.append("Content-Type: text/plain\r\n")
.append("Content-Length: ").append(body.length()).append("\r\n")
.append("\r\n");
try{// 开始响应
resp.write(response.toString().getBytes());
resp.write(body.getBytes());
return true;
}
catch (Exception e){
e.printStackTrace();
return false;
}
}
响应消息构建
该函数负责对不同的请求信息和请求内容进行消息响应,主要是http
报文格式的构建和发送过程,具体消息体目前很简单,只要返回通过pass
或是拒绝reject
两种报文即可。
具体实现流程为,构建http
报文头,获取消息体body
,在输出流write
函数中写入,具体代码如下:
public static boolean Response(OutputStream resp,String body){
StringBuilder response = new StringBuilder();
// 构造响应消息头
response.append("HTTP/1.1 ").append(200).append("\r\n")
.append("Content-Type: text/plain\r\n")
.append("Content-Length: ").append(body.length()).append("\r\n")
.append("\r\n");
try{// 开始响应
resp.write(response.toString().getBytes());
resp.write(body.getBytes());
return true;
}
catch (Exception e){
e.printStackTrace();
return false;
}
}
应用中,检查用户名密码或注册成功状态后,分别给消息体body
中填充不同的字段即可,过程代码如下:
if(posttype.equals("1")){//登陆逻辑
System.out.println("请求类型为:登录");
//判断用户名和密码是否正确
//正确返回用户记录,错误返回reject
if( test.check_user(t,username,password)){
Response(resp,record);
}
else{
Response(resp,"reject");
}
}
else if(posttype.equals("2")) {//注册逻辑
System.out.println("请求类型为:注册");
if( test.add_user(t,username,password)){
Response(resp,"pass");
System.out.println("注册成功");
}else{
Response(resp,"reject");
}
}
else if(posttype.equals("3")){
System.out.println("请求类型为:更新");
if(test.update_userRecord(t,username,password)){
Response(resp,"pass");
System.out.println("更新记录成功");
}
}
else{
System.out.println("请求类型为:其他");
}
总结
该项目断断续续也算搞了小两周,费事的主要难点在于:
1,前端语法不熟悉。构建框架,组件使用,几乎每一个都要上网查一下才能确定,这导致进展缓慢。
2,实际应用场景复杂。网络通信相互依赖时的同步处理,这是我印象最深,卡的最久的,另外数据库操作是否立即提交,页面跳转传递信息等,这都是很小很细节的点,但如果考虑不到都会成为一个坑。
目前想到后续的改进工作如下:
1,手动编写的登陆验证可以直接拉起微信接口,更贴近实际。
2,后端数据更多的展示,好友排行榜等提示信息,增加社交属性。
3,游戏方案优化,增加更多动画,子弹和敌机,丰富场景。
虽然项目还很稚嫩,但对我来说作为一个打通前后端交互的小demo还是学到了很多,前端页面大致的build
过程,报文传递与解析,还有同步化,第一次写前端,如果以后不需要从事该岗位的话,了解到此应该也足够了吧。