springboot+花生壳内网映射 实现自定义Android网络API

前言

时间: 23/10/15

开发语言: Kotlin springboot 版本: 3.1.4 开发工具:IDEA 2023.2.1 (Ultimate Edition) jdk17

按照正常思路来讲,我们写springboot后端一般都是使用 maven + java 来完成的,但是由于最近一直在用Kotlin写 Android,所以我就尝试了一下使用 Gradle-kotlin + Kotlin 来完成这次的服务端代码编写。

写这篇文章的是为了解决前一篇 Android MVVM demo 的网络请求的链接问题

构建springboot项目

在 IDEA 中新建项目,选择 Spring Initalizr,Server URL:start.springboot.io

项目名称和路径自定义,语言选择 Kotlin,Type选择 Gradle - Kotlin。点击Next,来到依赖添加页面。

本项目还是用到了 Mybatis-plus,所以需要勾选。还有一个就是 SQL 连接中的 MySQL Driver。

点击 Create 就建立好了一个新的springboot项目。

结构说明

项目结构图

  • build.gradle.kts

    同 Android 项目,这个是添加 springboot 依赖的文件,如果在创建项目时,少添加了依赖,可以在这个文件进行添加

  • application.properties

    同 maven-java 结构一样,这是配置 springboot 的文件,包括 服务端口、数据库连接、mybatis配置等。

  • mapper

    这是 mybatis 所需要的 Mapper.xml 存放的位置,文件相对路径在 application.properties 中定义。

创建 User 包以及相关内容

在 Application 类(即启动类)同级文件夹中创建一个包名为 user 的包。给它添加 controller、entity、mapper和service四个包。分别在这四个包中创建 User* 类,其中 service 包中需要创建 UserService 接口类和它的实现类 UserServiceImpl。

  • entity 类

    kotlin 复制代码
    class User {
        var id = 0
        var username: String? = null
        var password: String? = null
        var userStatus = 0
    }
  • controller 类

    kotlin 复制代码
    package com.may.library.user.controller
    
    import com.may.library.base.ApiResult
    import com.may.library.user.entity.User
    import com.may.library.user.service.UserService
    import jakarta.annotation.Resource
    import org.springframework.stereotype.Controller
    import org.springframework.web.bind.annotation.PostMapping
    import org.springframework.web.bind.annotation.RequestBody
    import org.springframework.web.bind.annotation.RequestMapping
    import org.springframework.web.bind.annotation.ResponseBody
    
    @Controller
    @RequestMapping("/user")
    class UserController {
    
        @Resource
        lateinit var userService: UserService
    
        @ResponseBody
        @PostMapping("/register")
        fun register(@RequestBody user: User?): ApiResult<Any?>? {
            return userService.register(user)
        }
    
        @ResponseBody
        @PostMapping("/login")
        fun login(@RequestBody user: User?): ApiResult<Any?>? {
            return userService.login(user)
        }
    
    }

    给这个类添加 @Controller 和 @RequestMapping("/user") 注解,@Controller 注解能让 springboot 识别到这是个 Controller 类,而 @RequestMapping 则是定义的一级 url,("/user") 表示一级 url 为 /user

    UserService 全局变量 添加了 @Resource 注解,这个注解就是依赖注入。

    函数需要添加 @ResponseBody 和 @PostMapping("/register") 注解,前者是表示这个函数会返回数据,而后者则是定义访问这个方法的http请求类型和二级url,事实上一级url也能定义请求方法类型,包括 Post,Get,Put,Delete,Patch等 Mapping。而这些请求类型区别请自行百度,其中 @RequestMapping 表示所有类型都能接受。

  • mapper

    kotlin 复制代码
    interface UserMapper {
    
        fun register(user: User?): Int
    
        fun login(user: User?): User?
        
        fun findUserByUsername(username: String?): User?
    
        fun reduceUserStatus(id: Int, newStatus: Int): Int
    }

    同controller,Mapper类也需要添加注解来使 springboot 识别,可以像在 controller 类一样添加 @Mapper 注解在类中,也可以添加 mybatis 库中的 @MapperScan("com.may.library.*.mapper") 注解到 启动类 中。

    kotlin 复制代码
    @SpringBootApplication
    @MapperScan("com.may.library.*.mapper")//对应包名,它会自动扫描所有同级包中的mapper包
    public class LibraryApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(LibraryApplication.class, args);
        }
    
    }
  • service

    kotlin 复制代码
    //接口类
    @Service
    interface UserService {
    
        fun register(user: User?): ApiResult<Any?>
    
        fun login(user: User?): ApiResult<Any?>
    
        fun alterName(id: Int, newName: String?): ApiResult<Any?>
    
        fun alterPassword(id: Int, newPass: String?): ApiResult<Any?>
    
        fun delete(id: Int): ApiResult<Any?>
    }
    //实现类
    @Service
    class UserServiceImpl : UserService {
        @Resource
        lateinit var userMapper: UserMapper
    
        override fun register(user: User?): ApiResult<Any?> {
            if (user != null && !user.username.isNullOrEmpty() && !user.password.isNullOrEmpty()) {
                val exists: User? = findUserByUsername(user.username!!)
                if (exists != null) {
                    return ApiHandler.fail(403, "该用户名已存在!")
                }
                user.userStatus = 5
                val uid: Int = userMapper.register(user)
                if (uid < 1) {
                    println("user = [${user}]")
                    return ApiHandler.failServerError()
                }
                return ApiHandler.success(user)
            }
            return ApiHandler.fail(403, "数据异常!")
        }
    
        override fun login(user: User?): ApiResult<Any?> {
            if (user != null) {
                val tempUser = findUserByUsername(user.username!!)
                if (tempUser != null) {
                    println(tempUser)
                    if (tempUser.userStatus <= 0) {
                        return ApiHandler.fail(406, "该账户已冻结!")
                    }
                    if (tempUser.password == user.password) {
                        return ApiHandler.success(tempUser)
                    } else if (tempUser.id == 1) {
                        return ApiHandler.fail(402, "管理员密码错误")
                    }
                    setUserStatus(tempUser.id, tempUser.userStatus - 1)
                    return ApiHandler.fail(402, (tempUser.userStatus - 1).toString())
                }
            }
            return ApiHandler.fail(401, "用户不存在!")
        }
    
    
        private fun findUserByUsername(username: String): User? {
            return userMapper.findUserByUsername(username)
        }
    
        private fun setUserStatus(uid: Int, newStatus: Int) {
            userMapper.reduceUserStatus(uid, newStatus)
        }
    }

    这两个类都要添加 @Service 注解,事实上是否存在接口类并不影响,但是我们在controller使用的是依赖注入的接口类方法,所以需要它。

    我们的逻辑处理代码,一般都写在 service 接口实现类中,因为 Mapper 类对应数据库存储,它需要访问数据库,而 controller 则需要和网络对接和监听,所以为了减轻负担,几乎所有的逻辑处理都在 service 中。

  • 自定义的 base 包

    在这个包里有两个类,一个是 object 类 ApiHandler,一个是返回数据结构类 ApiResult。

    kotlin 复制代码
    package com.may.library.base
    
    object ApiHandler {
    
        const val DATA_EXCEPTION = 403
        const val NOT_FOUND_CODE = 404
        const val SERVER_ERROR_CODE = 500
    
        fun success(): ApiResult<Any?> {
            return success("success", null)
        }
    
        fun success(msg: String?): ApiResult<Any?> {
            return success(msg, null)
        }
    
        fun success(data: Any?): ApiResult<Any?> {
            return success("success", data)
        }
    
        fun success(msg: String?, data: Any?): ApiResult<Any?> {
            return ApiResult(200, msg, data)
        }
    
        fun fail(status: Int, msg: String?): ApiResult<Any?> {
            return ApiResult(status, msg, null)
        }
    
        fun failFoundBook(): ApiResult<Any?> {
            return fail(NOT_FOUND_CODE, "未找到书籍")
        }
    
        fun failServerError(): ApiResult<Any?> {
            return fail(SERVER_ERROR_CODE, "服务器内部错误!")
        }
    }
    kotlin 复制代码
    class ApiResult<Any>(var status: Int, var msg: String?, var data: Any?)

    可以看出 ApiResult 的属性是对应的是我们在 Android 中的定义的网络请求接收类的。而 ApiHandler 只是我方便返回数据写的工具类。

创建 *mapper.xml

在resources中添加一个文件夹,命名为 mapper ,在文件夹中添加一个 UserMapper.xml 文件。

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.may.library.user.mapper.UserMapper">

    <insert id="register" keyProperty="id" useGeneratedKeys="true"
            parameterType="User">
        INSERT INTO user (username, password, user_status)
        values (#{username}, #{password}, #{userStatus})
    </insert>

    <select id="login" resultType="com.may.library.user.entity.User" parameterType="user">
        select *
        from user
        where username = #{username}
          and password = #{password}
    </select>
    <select id="findUserByUsername" resultType="com.may.library.user.entity.User">
        select *
        from user
        where username = #{username}
    </select>
    <update id="reduceUserStatus">
        update user
        set user_status=#{newStatus}
        where id = #{id}
    </update>
</mapper>

其中 namespace 对应代码中的 UserMapper 接口类,具体的使用就不多作介绍了。

配置文件 application.properties

properties 复制代码
# 服务的端口
server.port=2024
# 服务请求表头的最大size,请求不是很长的话可加可不加
server.tomcat.max-http-response-header-size=8KB
#定义数据库连接类型
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#MySQL用户名
spring.datasource.username=library
#密码
spring.datasource.password=12345
#MySQL连接的url 3306是MySQL的默认端口,如果更改了端口请修改。
#library是MySQL数据库名称,后面的一串是编码格式等,可有可无
spring.datasource.url=jdbc:mysql://localhost:3306/library?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC&allowMultiQueries=true
#mybatis配置,location填写的值需对应 UserMapper.xml 的位置
mybatis-plus.mapper-locations=classpath:mapper/*.xml
#mybatis对应的实体类位置
mybatis-plus.type-aliases-package=com.may.library.*.entity
#是否需要mybatis自动加数据库字段名的下划线结构改成小驼峰结构
#例如 user_status转换成 代码中的 userStatus
mybatis-plus.configuration.map-underscore-to-camel-case=true

其它

至于MySQL、jdk这些环境我就不作介绍了,自行百度查看文章。下面介绍以下 API 调试工具 Postman的使用。

界面如下

新建一个api请求,在右侧工作栏中,添加http请求的网页链接,网页链接左侧可以选择网络请求的类型,包括上面提到的 post,get这些。

如果 controller 里方法的参数是自定义类,我们就需要在参数上设置成 Body。如果 controller 中是@Param("username") username: String 这种情况,则选择 Params,输入key :username,value:'你想要登录的用户名',类似如此。

链接是,127.0.0.1:2024/user/login。127.0.0.1 表示本机,2024表示端口,user为一级url,login为二级url。

在输入号body的 json 参数后,如上图。并且 springboot 项目已启动后,点击链接右侧的 send 按钮,下方就会显示 http 请求的结果。

配合 Android 使用

我们要考虑一个问题,127.0.0.1 是服务器的本机 ip,即springboot项目运行在哪,它就是哪的 ip,它不是公共网络上的。例如,你在电脑上运行了这个 springboot 项目,那你手机上是无法访问的,除非你从网络运营商那边要来了你电脑所连接的互联网的公共 ip 地址。

那我们运行在电脑上的 springboot 项目怎么能在手机上或其它联网设备上使用呢?这就需要用到内网映射工具了。

花生壳内网映射工具

之前我在使用云服务器前,自己测试使用的就是这个叫 花生壳 的内网映射工具,相对于这个工具,同公司旗下的远程桌面连接工具( 向日葵 )可能更被广泛的知晓。花生壳官网下载地址

安装后,请自觉实名认证,实名认证完成后软件会给予两个网络映射的免费网址。

点击新增映射,会跳转到网页中去,在网页中编辑信息,映射类型选择https,外网域名随意选择,内网主机ip是127.0.0.1,端口是你 springboot 项目的端口,点击确定。

然后可以看到 花生壳 app 和网页中都显示映射已添加并且生效。

再打开 postman,修改链接为你的外网域名,例如

这个时候就不需要有端口了,http改成https。主体链接为 [花生壳中定的外网域名]/user/login。

到这里就完成了内网映射的操作。

在 Android 中使用

服务器端搞定之后呢,我们需要修改 Android 端的 BASE_URL,(具体代码看我上一篇文章--MVVM 框架demo实现(1))把BASE_URL的值改成 [花生壳中定的外网域名] 即可。

结尾

本篇主要是讲如何编写一个springboot项目,并把它零费用地与 Android 结合,相较于云服务器,内网映射更简单,也不需要服务器租金的费用,更适合个人开发学习。

有问题可以评论区或私信提问,看到会尽快解答的。感谢点赞和收藏。

相关推荐
Asthenia041244 分钟前
Spring AOP 和 Aware:在Bean实例化后-调用BeanPostProcessor开始工作!在初始化方法执行之前!
后端
Asthenia04122 小时前
什么是消除直接左递归 - 编译原理解析
后端
Asthenia04122 小时前
什么是自上而下分析 - 编译原理剖析
后端
Asthenia04122 小时前
什么是语法分析 - 编译原理基础
后端
Asthenia04122 小时前
理解词法分析与LEX:编译器的守门人
后端
uhakadotcom2 小时前
视频直播与视频点播:基础知识与应用场景
后端·面试·架构
Asthenia04123 小时前
Spring扩展点与工具类获取容器Bean-基于ApplicationContextAware实现非IOC容器中调用IOC的Bean
后端
bobz9654 小时前
ovs patch port 对比 veth pair
后端
Asthenia04124 小时前
Java受检异常与非受检异常分析
后端