• 基于Android的JavaEE课设


    目录

     

    1 技术栈

    2 android前端

    2.1 概述

    2.1.1 目录结构

    2.1.2 代码分层

    2.2 技术点

    2.2.1 数据绑定

    2.2.2 前后端数据交互

    2.2.3 九宫格图片

    2.2.4 未处理消息提醒

    2.2.5 动画效果

    2.2.6 实时聊天

    2.2.7 文件上传

    2.2.8 底部弹窗

    2.2.9 其他

    3 后端

    3.1 概述

    3.1.1 目录结构

    3.2 技术点

    3.2.1 viewmodel

    3.2.2 文件上传

    3.2.3 websocket

    4 服务器相关

    4.1 搭建环境遇到的问题

    4.1.1 本地访问不了远程mysql数据库

    5 前后端交互设计文档

    6 后台管理系统

    6.1 搭建步骤

    6.1.1 初始化本地仓库

    6.1.2 从Gitee上拉取项目

    6.1.3 配置、运行renren-fast项目

    6.1.4 配置、运行renren-fast-vue项目

    6.1.5 配置,运行 renren-generator

    6.2 过程中遇到的问题

    6.3 最终呈现效果


     

    1 技术栈

    前端:

    • android

    后端:

    • springboot
    • springsecurity
    • mybatis-plus
    • redis
    • websocket

    项目部署阿里云服务器

     

    2 android前端

    2.1 概述

    主要介绍引用的第三方框架、技术点

    2.1.1 目录结构

    1fc9ebb260d14d0ba2493424b9ceb9bc.png

     

    2.1.2 代码分层

     

    不论是目录结构,还是代码,都应注意分层

    69099c6601d8454e9068c3f303b1aa8b.png

     

    2.2 技术点

    2.2.1 数据绑定

    viewBinding

    1. android {
    2. buildFeatures {
    3. viewBinding true
    4. }
    5. }

    2c5be7adf52e4be8b78d4efc95534db9.png

     

     

    2.2.2 前后端数据交互

    xutils

    引入依赖

    1. implementation 'org.xutils:xutils:3.8.5'
    2. // gson
    3. implementation 'com.google.code.gson:gson:2.8.2'

     

     

    2.2.3 九宫格图片

    AssNineGridView

    ba9504ceec4c4ecd877d0407607d9c77.png

    2.2.4 未处理消息提醒

    badgeview

    f842e16c68c84ebca526fa739ca7a59d.png

     

    2.2.5 动画效果

    lottie

    1df5e8fac10e4d4584c44d0bdab4d023.pngd9c1d5d2c7fc4aa39676082bce56c24d.png

     

     

    2.2.6 实时聊天

    服务器 + websocket

    e0a557d209624137b5f8240b5bf173c2.jpeg

     

     

    2.2.7 文件上传

    单文件、多文件

     

    2.2.8 底部弹窗

    2721092370dc4be9a6a8a5942cee5cea.jpeg

     

    2.2.9 其他

    走马灯

    1. <TextView
    2. android:id="@+id/zm_tv"
    3. android:layout_width="260dp"
    4. android:layout_height="wrap_content"
    5. android:layout_marginTop="32dp"
    6. android:ellipsize="marquee"
    7. android:focusable="true"
    8. android:focusableInTouchMode="true"
    9. android:marqueeRepeatLimit="-1"
    10. android:padding="10dp"
    11. android:singleLine="true"
    12. android:text="@string/trotting_horse_lamp"
    13. android:textColor="@color/baby_blue"
    14. android:textSize="20dp"
    15. app:layout_constraintHorizontal_bias="0.497"
    16. app:layout_constraintLeft_toLeftOf="parent"
    17. app:layout_constraintRight_toRightOf="parent"
    18. app:layout_constraintTop_toTopOf="parent">
    19. <requestFocus />
    20. TextView>

    输入提示属性

    android:hint="请输入用户名"

    文本改变监听器

    1. searchEditText.addTextChangedListener(new TextWatcher() {
    2. @Override
    3. public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
    4. @Override
    5. public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
    6. @Override
    7. public void afterTextChanged(Editable editable) {
    8. // 文本改变后执行的动作
    9. }
    10. });

    webView

    spinner

     

     

    3 后端

    3.1 概述

    3.1.1 目录结构

    ff5eaa15efd64f78a214992bf35d3b61.png

    3.2 技术点

    3.2.1 viewmodel

    使用场景:比如用户有多个字段,但是当我们注册,登录时,并不需要这么多字段

    好处:节省数据交互时空间的消耗,有提升效率的作用。

     4f20c249a3124aa3a73dbb81b6ae4365.png

     实际上,我们需要的字段是比较少的,比如:

    64cd0e2dae3b419699445689e9b708a8.png

     在UserController层:

    dda7100ef7394421a5c0a4a813184843.png

    不过在最后,我们存入数据库的肯定还是原来的User,而不是UserRegisterVM, 所以,我们要对这两者之间作个转换:

    User user = modelMapper.map(model, User.class);

    以下是对modelMapper的封装,可以当做API调用。

    1. public class BaseApiController {
    2. /**
    3. * The constant DEFAULT_PAGE_SIZE.
    4. */
    5. protected final static String DEFAULT_PAGE_SIZE = "10";
    6. /**
    7. * The constant modelMapper.
    8. */
    9. protected final static ModelMapper modelMapper = ModelMapperSingle.Instance();
    10. }

    需要使用modelMapper的时候,要继承BaseApiController 这里对应的是一个单例modelMapper

    1. public class ModelMapperSingle {
    2. /**
    3. * The constant modelMapper.
    4. */
    5. protected final static ModelMapper modelMapper = new ModelMapper();
    6. private final static ModelMapperSingle modelMapperSingle = new ModelMapperSingle();
    7. static {
    8. modelMapper.getConfiguration().setFullTypeMatchingRequired(true);
    9. modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);
    10. }
    11. /**
    12. * Instance model mapper.
    13. *
    14. * @return the model mapper
    15. */
    16. public static ModelMapper Instance() {
    17. return modelMapperSingle.modelMapper;
    18. }
    19. }

     

    3.2.2 文件上传

    3e64a78458ef49f8bb1d6de62d59b1f4.png

     

    1. /*
    2. * @description: 将文件保存在本地
    3. * @author: xingxg
    4. * @date: 2022/11/8 17:32
    5. * @param: file , 与前端的名字相对应
    6. * @return: 返回文件保存的路径
    7. **/
    8. @PostMapping("/upload")
    9. public CommonResult> fileLoad(MultipartFile[] file, HttpServletRequest request) throws IOException {
    10. String saveLocation = "e:/images/";
    11. //String saveLocation = "/images/";
    12. String fileSaveName = "";
    13. List imageUri = new ArrayList<>();
    14. for (MultipartFile multipartFile : file) {
    15. fileSaveName = UUID.randomUUID().toString() + multipartFile.getOriginalFilename();
    16. multipartFile.transferTo(new File(saveLocation, fileSaveName));
    17. String result = request.getScheme() + "://" +
    18. request.getServerName() + ":" +
    19. request.getServerPort() + "/" +
    20. fileSaveName;
    21. imageUri.add(result);
    22. }
    23. return CommonResult.ok(imageUri);
    24. }

     

    3.2.3 websocket

    此前的状态是,两个人进行聊天,A发一句,B需要重新进入聊天界面才能看到A新发的消息,这是不合理的,针对该问题进行改进。

    预期效果,不用重新进入页面,只要由新消息传进来,就可以展示新的数据。

    解决方法1:

    前端周期性地去请求后端数据,达到实时聊天的效果。

    这种方式非常消耗资源,明显是不合理的。

    解决方法2:

    A向B发消息, A将消息推送到服务器上,由服务器主动推动消息给B

     android

    引入依赖

    implementation "org.java-websocket:Java-WebSocket:1.3.7"

    android核心代码

    1. public void addUserToService() {
    2. URI serverURI = URI.create("ws://192.168.130.1:9000/chat/" + Global.username);
    3. webSocketClient = new WebSocketClient(serverURI) {
    4. @Override
    5. public void onOpen(ServerHandshake handshakedata) {
    6. Log.i("WebSocketClient", "onOpen");
    7. }
    8. @Override
    9. public void onMessage(String message) {
    10. loadMessage(); // 有新消息传给本用户的话,重新请求后端,加载数据。
    11. }
    12. @Override
    13. public void onClose(int code, String reason, boolean remote) {
    14. Log.i("WebSocketClient", "onClose");
    15. }
    16. @Override
    17. public void onError(Exception ex) {
    18. Log.i("WebSocketClient", "onError");
    19. }
    20. };
    21. try {
    22. webSocketClient.connectBlocking();
    23. } catch (InterruptedException e) {
    24. e.printStackTrace();
    25. }
    26. }

    后端核心代码

    1. package com.huiliyi.backend.utils;
    2. import com.fasterxml.jackson.databind.ObjectMapper;
    3. import lombok.Data;
    4. import lombok.extern.slf4j.Slf4j;
    5. import org.springframework.stereotype.Component;
    6. import javax.websocket.*;
    7. import javax.websocket.server.PathParam;
    8. import javax.websocket.server.ServerEndpoint;
    9. import java.util.Map;
    10. import java.util.concurrent.ConcurrentHashMap;
    11. @Slf4j
    12. @ServerEndpoint(value = "/chat/{username}")
    13. @Component
    14. @Data
    15. public class ChatEndpoint {
    16. //用来存储每个用户客户端对象的ChatEndpoint对象
    17. public static Map onlineUsers = new ConcurrentHashMap<>();
    18. //声明session对象,通过对象可以发送消息给指定的用户
    19. private Session session;
    20. private String username;
    21. //连接建立
    22. @OnOpen
    23. public void onOpen(@PathParam("username") String username,
    24. Session session, EndpointConfig config){
    25. this.session = session;
    26. this.username = username;
    27. //存储登陆的对象
    28. onlineUsers.put(username,this);
    29. log.info("onOpen:{}", username);
    30. }
    31. //收到消息
    32. @OnMessage
    33. public void onMessage(String toName,Session session){
    34. //将数据转换成对象
    35. try {
    36. //发送数据
    37. log.info("onMessage:{}",toName);
    38. onlineUsers.get(toName).session.getBasicRemote().sendText("来消息了");
    39. } catch (Exception e) {
    40. e.printStackTrace();
    41. }
    42. }
    43. //关闭
    44. @OnClose
    45. public void onClose(Session session) {
    46. //从容器中删除指定的用户
    47. log.info("onClose:{}", username);
    48. onlineUsers.remove(username);
    49. }
    50. }
    public static Map onlineUsers = new ConcurrentHashMap<>();

    系统中用户名昵称是全局唯一的,所以可以将用户名作为主键,使得每个用户对应一个ChatEndpoint

     

     

    4 服务器相关

    采用宝塔面板进行傻瓜式操作。

    注意在阿里云的云服务器实例开放所需要的所有端口

    4.1 搭建环境遇到的问题

    4.1.1 本地访问不了远程mysql数据库

    在linux本地可以登录mysql, 但是本地机连不上远程的?

    a026ba80e68048c2800d99b7126d2254.png

      原因是远程主机没有开放3306端口

     

    开放3306端口号后,还是出现问题:

    828986e3275c4dbd84c9a069cf58c70f.png

     

    这是因为远程的 主机限制了只能localhost连接数据库

    所以,在远程主机上修改这个限制,就可以解决这个问题了

    1、在安装Mysql数据库的主机上登录root用户:

    mysql -u root -p

    2、执行命令:

    1. use mysql;
    2. select host from user where user='root';

    可以得到结果:

    90cbca793e644a1590808c1aa1511e71.png

    表名此时mysql数据库只允许localhost连接,所以此前才会拒绝本地的主机连接远程服务器。

    3、将Host设置为通配符%

    update user set host = '%' where user ='root';

    4、Host修改完成后记得执行flush privileges使配置立即生效

    flush privileges;

    dc7cd681758e47bdba7e74710ed8b3c5.png

     

     

    5 前后端交互设计文档

    使用Apipost

    d9b5ca64a7fa4fa8abcf704c2faac334.png

     

    在课程设计的过程中,项目采用前后端分离技术。

    有的人负责编写后端,有的人负责编写前端。这可以使得整个项目的结构更加清晰明了,但是前端如何去与后端交互是一个问题。

    因为前后端是不同的人写的,所以需要提前规范好API文档

    而ApiPost不仅可以对API接口进行调试,还可以自动生成相对应的文档,是一个非常实用的开发的工具。

     

    3742662c5dc0475dadc74a4754ee1590.png

     生成的文档,就比较清晰,比自己写一个API接口文档要规范的多

    00e554577fa8422dad882dd1f44d7332.png

    6 后台管理系统

    renren.io

    人人开源是Gitee上的一个开源项目,可以快速搭建后台管理系统

    9b8d1fdf5758491095161153518e7596.png

     

    6.1 搭建步骤

    6.1.1 初始化本地仓库

    1、 创建一个总目录,用来承载多个项目

     

    360c4c427653495db0e9084c0817e43e.png

     

    2、初始化为git仓库

    进入test_project0684f6eb027e4afe98683c18cfa48e87.png

     27f7be7985984558ba1075fa954b13cb.png

     

     

    6.1.2 从Gitee上拉取项目

     

    5160607295384e4185e71a1f06a4938d.png

     4c7999d395504bf591edc2a40a30544c.png

     

     

     

    同理,再拉取2个项目

    c1d5600a1a3a47dc938d39bda495dfcd.png

     

    0fb2297b35ba41a8a476e482bb91572c.png

     

    最终的目录层次为:

    9d987187002b4cc6a6f4c342153fed12.png

     

    6.1.3 配置、运行renren-fast项目

    f43ea23ee7304fdc8c94164a55bb7b34.png

     b4ac58f589254489977db6e92c6de196.png

     f197ecd99c9b4c44b986f07812221b1d.png

     0517e334e7e44de0a88e87d19f06286b.png

     

     

    运行项目, 访问Swagger文档路径,如果可以正常访问,则表明一切正常。

    4f781acd25f5423899ba6cb769e9a189.png

     04b0b13bb75f440f8d3b2175438a87c1.png

     

     

    6.1.4 配置、运行renren-fast-vue项目

    打开renren-fast-vue项目, 这里本人使用的是WebStorm编译器

    1b198fe21fce40048a2c683e383adea1.png

     

    依赖安装完成, 打开终端,运行项目

    这里的命令行是:

     npm run dev

    5b63485463954ff88cfaeaf6bdddaee1.png

     f36b15bf9afa4e5780d4bae3850bba3d.png

    登录成功后:

    7ae0f2a5c5384057a6540a30267a6dd6.png

     

    此时,这个项目还算是一个空壳,没有管理到真正自身的项目

    这是就需要另一个项目, renren-generator 实现代码生成!

    代码生成需要提供关于数据库的表


    6.1.5 配置,运行 renren-generator

    e1e7594a481c478aaeb4538262997441.png

     7be8f2c148e447eba3d4190921f4f5fd.png

     

    修改generator.properties

    d0fff75688b74e19933f6edea6ba6cb3.png

     

    注释出现乱码问题,修改字符集编码

    6e64d6c50f754869be932e21c807c9ca.png

     7dba7cb1e1a84f4682297bd1bc10a767.png

     

     

    在renren_fast数据库中插入你真正项目中所使用的表, 可以在已有的数据库中选择转储sql文件,然后在renren_fast中执行这个sql文件即可。

    34f073ddac2942a2b32d074b2e4b81e5.png

     

    然后,就可以在代码生成器中查到你刚刚插入的数据库表。

    运行项目,访问localhost

    946a061f158e4a65afe322b4deeb1378.png

     03a31b07552a40b6aaf599f1edffaaf5.png

     

     

    代码就自动生成了。

    解压文件,可以看到有如下内容:

     

    98d39dc786cc4be9890596bc27b0215e.png

     

    这些sql文件,直接拖到navicat里执行就可以了。

    作用是用来生成管理的子菜单

    a9bc1cd926a542938ce1ba8c3dad48d1.png

     441c9c4f001b432b9121ff1ed160af42.png

     

    将后端代码复制到renren-fast项目下的 module

    94d60a9d336d44f58544efbdb3992110.png

     

    最后,就可以实现对模块的管理。

    33d6410e509743cc951b4968577317f0.png

     

     

     

    6.2 过程中遇到的问题

    人人开源后端中,自带了一个User,而我们自身模块有一个名字一模一样的User

     

    冲突1:

    • Spring容器中UserService组件不唯一

    解决:

    cd18836755734488940eac2e86ba89e5.png

     

    ea07ed19431242f796af956017718f1f.png

     

     

    冲突2:

    • UserDao也是自动注入的。虽然说对应的mapper文件可以映射过去,但是名字重复了,还是会有点问题.

    解决方法:

    83d31a7fa51d4190b1242d3598e7dad8.png

     

     

    冲突3:

    • ShiroConfig 已经存在对UserEntity的映射。意思就是说,类名不能重复。

    解决方法:修改类名

    fc39e65145e746d0bb5a0a7b0cd27f25.png

     

     

     

    6.3 最终呈现效果

    908c2956b08f45a381159b1464c47940.png

     

    可以看到,用户的ID后几位都为0,这是由于前段接收Long类型时,出现精度丢失,以下为解决方式:

    7729a82e4545463b874d7da41b9ba4ec.png

     

    72bd1654bfb146b19083ebeae210ab4e.png

     

     

     

  • 相关阅读:
    RabbitMQ面经
    Java8 stream处理List,Map总结
    【牛客】SQL130 试卷发布当天作答人数和平均分
    SQL server 根据子级查询根父级
    IDEA集成Git
    C++学习day3
    Codeforces Round #809 (Div. 2) A~D
    JAVAFX学习
    GitLab 查看版本信息
    QML动画
  • 原文地址:https://blog.csdn.net/PURSUE__LIFE/article/details/128068364