Spring Boot、Netty、CoCos Creator、MySql 登录服务

简介

本项目用了

  • Spring Boot 2.5.4
  • Netty 4.1.66.Final
  • CoCos Creator 2.4.6
  • Json fastjson 1.2.78
  • mybatis 2.2.0
  • MySql
  • MD5 加密
  • TypeScripts
    ccc客户端仅用了Post请求,数据库对密码进行MD5加密,保证用户信息安全

参考文章

  1. Netty构建游戏服务器
  2. Spring Boot 返回 JSON 数据及数据封装
  3. MD5加密

一、netty

项目结构

在这里插入图片描述

pom.xml

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--web开发-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--netty-->
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.66.Final</version>
        </dependency>
        <!--mybatis-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.0</version>
        </dependency>
        <!--mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!--druid的starter-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.6</version>
            <scope>runtime</scope>
        </dependency>
        <!--json-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.78</version>
        </dependency>
        <!--MD5加密 对注册的密码进行加密操作-->
        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.12.0</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.5.4</version>
            </plugin>
        </plugins>
        <!--指明.xml 资源文件-->
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
            </resource>
        </resources>
    </build>

application.properties

#mysql
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql://localhost:3306/user

NettyServer

package com.cocos.login.netty;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;

@Service("nettyServer")
public class NettyServer {
    private static final int port = 8080;
    private NioEventLoopGroup boss = new NioEventLoopGroup();
    private NioEventLoopGroup worker = new NioEventLoopGroup();

    @Autowired
    private MyChannelInitializer myChannelInitializer;

    @PostConstruct
    public void start() {
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(boss,worker)
                    .channel(NioServerSocketChannel.class)
                    .handler(new LoggingHandler(LogLevel.DEBUG))
                    .option(ChannelOption.SO_BACKLOG,1024)
                    .childHandler(myChannelInitializer);
            ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            boss.shutdownGracefully();
            worker.shutdownGracefully();
        }
    }
}

MyChannelInitializer

package com.cocos.login.netty;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.json.JsonObjectDecoder;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
@Service("myChannelInitializer")
public class MyChannelInitializer extends ChannelInitializer<SocketChannel> {
    @Resource
    private ServerHandler serverHandler;

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ch.pipeline().addLast("myCodec",new HttpServerCodec());
        //发送大量数据浏览器会响应多个http请求,用一个处理器将多个段聚合
        ch.pipeline().addLast("myAggregator",new HttpObjectAggregator(64 * 1024));
        //json格式解码器,当检测到匹配数量的"{" 、”}”或”[””]”时,则认为是一个完整的json对象或者json数组。
        ch.pipeline().addLast(new JsonObjectDecoder());
        ch.pipeline().addLast("myServerHandler",serverHandler);
    }
}

ServerHandler

package com.cocos.login.netty;

import com.alibaba.fastjson.JSON;
import com.cocos.login.pojo.User;
import com.cocos.login.service.UserService;
import com.cocos.login.utils.Md5Utils;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.util.CharsetUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service("myServerHandler")
@ChannelHandler.Sharable
public class ServerHandler extends SimpleChannelInboundHandler<HttpObject> {
    @Autowired
    UserService userService;

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
        if (msg instanceof FullHttpRequest) {
            FullHttpRequest httpRequest = (FullHttpRequest) msg;
            System.out.println("请求的Uri为:" + httpRequest.uri());

            if (httpRequest.method() == HttpMethod.POST) {
                String string = httpRequest.content().toString(CharsetUtil.UTF_8);
                User user = JSON.parseObject(string, User.class);
                System.out.println(user.toString());

                if (httpRequest.uri().equals("/login")) {
                    User userById = userService.getUserById(user.getId());

                    if (Md5Utils.code(user.getPassword()).equals(userById.getPassword())) {
                        FullHttpResponse response = Response(JSON.toJSONString(userById));
                        ctx.writeAndFlush(response);
                    } else {
                        ctx.writeAndFlush(Response("0"));
                    }
                } else if (httpRequest.uri().equals("/add")) {
                    User addUser = new User(user.getId(),"tom", Md5Utils.code(user.getPassword()),0,"hello");
                    int result = userService.addUser(addUser);
                    ctx.writeAndFlush(Response(JSON.toJSONString(result)));
                }
            } else {
                System.out.println("仅支持POST请求");
            }
        }
    }

    //响应
    private FullHttpResponse Response(String string){
        ByteBuf content = Unpooled.copiedBuffer(string, CharsetUtil.UTF_8);
        FullHttpResponse response =
                new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,HttpResponseStatus.OK,content);
        response.headers().set(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN,"*");//解决跨域问题
        response.headers().set(HttpHeaderNames.CONTENT_TYPE,"text/plain; charset=UTF-8");
        response.headers().set(HttpHeaderNames.CONTENT_LENGTH,content.readableBytes());
        return response;    //返回响应
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("连接的客户端地址:" + ctx.channel().remoteAddress());
        super.channelActive(ctx);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("异常了......");
        ctx.close();
        super.exceptionCaught(ctx, cause);
    }
}

实体类User

package com.cocos.login.pojo;

public class User {
    private String id;
    private String username;
    private String password;
    private int heroCount;
    private String message;

    public User(String id, String password) {
        this.id = id;
        this.password = password;
    }

    public User(String id, String username, String password, int heroCount, String message) {
        this.id = id;
        this.username = username;
        this.password = password;
        this.heroCount = heroCount;
        this.message = message;
    }
    //省略get 、set、toString方法

用了Mybatis框架,所有有mapper层
UserService

package com.cocos.login.service;

import com.cocos.login.mapper.UserMapper;
import com.cocos.login.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class UserService {
    @Autowired
    UserMapper userMapper;

    public int addUser(User user) {
        return userMapper.addUser(user);
    }

    public int deleteUserById(String id) {
        return userMapper.deleteUserById(id);
    }

    public int updateUserById(User user) {
        return userMapper.updateUserById(user);
    }

    public User getUserById(String id) {
        return userMapper.getUserById(id);
    }

    public String getPwdById(String id) {
        return userMapper.getPwdById(id);
    }

    public List<User> getAllUsers() {
        return userMapper.getAllUsers();
    }
}

UserMapper

package com.cocos.login.mapper;

import com.cocos.login.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;

@Mapper
public interface UserMapper {
    int addUser(User user);
    int deleteUserById(String id);
    int updateUserById(User user);
    User getUserById(String id);
    String getPwdById(String id);
    List<User> getAllUsers();
}

UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.cocos.login.mapper.UserMapper">
    <insert id="addUser" parameterType="com.cocos.login.pojo.User">
        INSERT INTO user(id,username,password,heroCount,message) VALUE (#{id},#{username},#{password},#{heroCount},#{message});
    </insert>
    <delete id="deleteUserById" parameterType="java.lang.String">
        DELETE FROM user WHERE id=#{id};
    </delete>
    <update id="updateUserById" parameterType="com.cocos.login.pojo.User">
        UPDATE user set password=#{password} WHERE id=#{id};
    </update>
    <select id="getUserById" parameterType="java.lang.String" resultType="com.cocos.login.pojo.User">
        SELECT * FROM user WHERE id=#{id};
    </select>
    <select id="getPwdById" parameterType="java.lang.String" resultType="java.lang.String">
        SELECT password FROM user WHERE id=#{id};
    </select>
    <select id="getAllUser" resultType="com.cocos.login.pojo.User">
        SELECT * FREE user;
    </select>
</mapper>

Md5Utils

package com.cocos.login.utils;


import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class Md5Utils {
    public static String code(String str){
        try{
            //1.获取MessageDigest对象  生成一个MD5加密计算摘要
            MessageDigest md = MessageDigest.getInstance("MD5") ;
            /*
            str.getBytes()
            * 使用平台的默认字符集将此 String 编码为 byte 序列,并将结果存储到一个新的 byte 数组中.
            此方法多用在字节流中,用与将字符串转换为字节。
            * */

            // 计算md5函数 使用指定的字节数组更新摘要md
            md.update(str.getBytes());
            /*
             * digest()最后确定返回md5 hash值,返回值为8的字符串。
             * 因为md5 hash值是16位的hex值,实际上就是8位的
             * */
            byte[] byteDigest = md.digest() ;
            int i ;
            StringBuffer buf = new StringBuffer("") ;
            //遍历byteDigest
            //加密逻辑,可以debug自行了解一下加密逻辑
            for(int offset = 0 ; offset<byteDigest.length ; offset++){
                i = byteDigest[offset] ;
                if(i < 0)
                    i += 256 ;
                if(i < 16)
                    buf.append("0") ;
                // BigInteger函数则将8位的字符串转换成16位hex值,用字符串来表示;得到字符串形式的hash值
                buf.append(Integer.toHexString(i)) ;
            }
            return buf.toString() ;
        }catch (NoSuchAlgorithmException e){
            e.printStackTrace();
            return null ;
        }
    }
}

LoginServerApplication

package com.cocos.login;

import com.cocos.login.netty.NettyServer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication
public class LoginServerApplication {

    public static void main(String[] args) {
        // 启动嵌入式的 Tomcat 并初始化 Spring 环境及其各 Spring 组件
        ConfigurableApplicationContext context = SpringApplication.run(LoginServerApplication.class, args);
        
        NettyServer nettyServer = context.getBean(NettyServer.class);
        nettyServer.start();
    }
}

二、数据库

名为user的数据库
名为user的表
字段根据user表创建
在这里插入图片描述
我们提前创建一个数据,配合/add请求,持久化一条含有md5的密码的数据,用于测试
在这里插入图片描述

到此服务器模块完成,先用postman测试一下,发现运行正常

三、CoCos Creator客户端

项目结构

在这里插入图片描述

打码部分是我测试用的,跟本项目无关,把未打码部分完成就好了,用到的组件在ts代码中查看。

user

export class User {
    private _id: string = "";
    private _username: string = "";
    private _password: string = "";
    private _heroCount: number = 0;
    private _message: string = "";

   constructor(id: string, password: string) {
       this._id = id;
       this._password = password;
   }


    public get id(): string {
        return this._id;
    }
    public set id(value: string) {
        this._id = value;
    }
    public get username(): string {
        return this._username;
    }
    public set username(value: string) {
        this._username = value;
    }
    public get password(): string {
        return this._password;
    }
    public set password(value: string) {
        this._password = value;
    }
    public get heroCount(): number {
        return this._heroCount;
    }
    public set heroCount(value: number) {
        this._heroCount = value;
    }
    public get message(): string {
        return this._message;
    }
    public set message(value: string) {
        this._message = value;
    }
}

http

const {ccclass, property} = cc._decorator;

@ccclass()
export class Http{
    /**
     * Get请求
     * @param url 站点:http://www.baidu.com
     * @param path 子路径 /index.html
     * @param params key1=value1&key2=value2&key3=value3
     * @param callback 当这个请求有回应的时候调用这个callback函数;
     * @returns 
     */
    get(url: string, path: string, params: string, callback: Function): any{
        var xhr = cc.loader.getXMLHttpRequest();
        xhr.timeout = 5000;
        var requestURL = url + params;
        if (params) {
            requestURL = requestURL + "?" + params;
        }

        xhr.open("GET", requestURL, true);
        if (cc.sys.isNative) {
            xhr.setRequestHeader("Accept-Encoding", "text/plain;charset=UTF-8");
        }

        xhr.onreadystatechange = function () {
            if (xhr.readyState === 4 && (xhr.status >= 200 && xhr.status < 300)) {
                console.log("http res("+ xhr.responseText.length + "):" + xhr.responseText);
            }
        }

        xhr.send();
        return xhr;
    }
    
    /**
     * POST请求
     * @param url 
     * @param path 
     * @param params 
     * @param body 数据body
     * @param callback 
     * @returns 
     */
    post(url: string, path: string, params: string, body: any, callback: Function) {
        var xhr = cc.loader.getXMLHttpRequest();
        xhr.timeout = 5000;
        var requestURL = url + path;
        if (params) {
            requestURL = requestURL + "?" + params;
        }

        xhr.open("POST", requestURL, true);
        if (cc.sys.isNative) {
            xhr.setRequestHeader("Accept-Encoding","text/plain;charset=UTF-8");
        }
        if (body) {
            xhr.setRequestHeader("Content-Type", "text/plain;charset=UTF-8");
        }

        xhr.onreadystatechange = function() {
            if(xhr.readyState === 4 && (xhr.status >= 200 && xhr.status < 300)){
                try {
                    //var ret = xhr.responseText;
                    var ret = JSON.parse(xhr.responseText);
                    if(callback !== null){
                        callback(null, ret);
                    }
                } catch (e) {
                    console.log("err:" + e);
                    callback(e, null);
                }
            }
        };
        
        if (body) {
            xhr.send(body);
        }
        return xhr;
    }

    download(url, path, params, callback) {
        var xhr = cc.loader.getXMLHttpRequest();
        xhr.timeout = 5000;
        var requestURL = url + path;
        if (params) {
            requestURL = requestURL + "?" + params;
        }

        xhr.responseType = "arraybuffer";  // 指定我们的数据类型

        xhr.open("GET",requestURL, true);
        if (cc.sys.isNative){
            xhr.setRequestHeader("Accept-Encoding","application/json;charset=UTF-8");
        }

        xhr.onreadystatechange = function() {
            if(xhr.readyState === 4 && (xhr.status >= 200 && xhr.status < 300)){
                var buffer = xhr.response;
                var data = new Uint8Array(buffer); // arraybuffer, new Unit8Array
                callback(null, data);
            }
        };

        xhr.send();
        return xhr;
    }
};

场景管理类 Manage

const {ccclass, property} = cc._decorator;

import { Http } from "./http";
import { User } from "./user";

@ccclass()
export class Manage extends cc.Component {
    @property(cc.Node)
    area_node: cc.Node = null;
    @property(cc.EditBox)
    user_node: cc.EditBox = null;
    @property(cc.EditBox)
    pwd_node: cc.EditBox = null;
    @property(cc.Button)
    btn_node: cc.Button = null;

    onLoad () {
        this.btn_node.node.on(cc.Node.EventType.TOUCH_END, this.login, this);
        //editing-did-began、editing-did-ended、text-changed、editing-return
        this.user_node.node.on("editing-did-ended", this.onEditDidEnded, this);
    }

    onDestroy(){
        this.btn_node.node.off(cc.Node.EventType.TOUCH_END, this.login, this);
        this.user_node.node.off("editing-did-ended", this.onEditDidEnded, this);
    }

    private login (): void {
        var user = new User(this.user_node.string, this.pwd_node.string);
        //JSON.parse():json字符串转对象、JSON.stringify():对象转json字符串
        let msg = JSON.stringify(user);

        const http = new Http();
        http.post("http://localhost:8080/", "login", null, msg, (err, data)=>{
            if (err) {
                console.log(err);
                return;
            }
            console.log(data);
        });
        
    }

    private onEditDidEnded() {
        // if (this.user_node.string === "") {
        //     return;
        // }
    }
}

到此客户端也完成了

四、运行

运行spring boot和CoCos Creator
在这里插入图片描述
此处时CoCos Creator代码中的简单打印,以后可以根据返回的对象值执行更多的操作
spring boot 后台打印

连接的客户端地址:/0:0:0:0:0:0:0:1:53926
请求的Uri为:/login
User{id='123456789', username='', password='1234556789', heroCount=0, message=''}
2021-08-29 22:18:54.340  INFO 692 --- [ntLoopGroup-3-1] com.alibaba.druid.pool.DruidDataSource   : {dataSource-1} inited
连接的客户端地址:/0:0:0:0:0:0:0:1:59113
请求的Uri为:/login
User{id='123456789', username='', password='123456789', heroCount=0, message=''}

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot是一个开源的Java web开发框架,而Netty是一个异步事件驱动的网络应用框架。一般情况下,Netty通常用于创建高性能的网络服务器和客户端。 如果要在Spring Boot中使用Netty服务器进行定时通知客户端,可以通过以下步骤实现: 1. 创建一个Netty服务器:首先,在Spring Boot项目中添加Netty的依赖项,然后编写一个类来创建Netty服务器。这个类需要实现ChannelHandler接口,并重写channelRead方法来处理接收到的消息。 2. 设置定时通知:在Netty服务器初始化完毕后,可以使用Java的定时任务来实现定时通知功能。可以使用ScheduledExecutorService类来定时执行指定的代码块。在代码块中,调用Netty服务器的write方法发送通知消息给客户端。 3. 客户端接收通知:客户端需要定义一个Netty客户端,并通过连接Netty服务器来接收通知消息。客户端需要实现ChannelHandler接口,并重写channelRead方法来处理接收到的通知消息。 4. 启动Spring Boot应用:在完成以上步骤后,可以启动Spring Boot应用,并同时启动Netty服务器和客户端。当定时任务触发时,服务器会发送通知消息给客户端。 需要注意的是,以上步骤只是大致的实现过程,具体的代码实现需要根据项目的需求进行适当的调整。同时,要确保服务器和客户端之间的通信方式和数据格式是一致的,以确保通信的顺利进行。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值