android:手搓一个即时消息聊天框(包含消息记录)

news2024/11/18 23:43:23

先看一下效果

1.后端

要实现这个,先说一下后端要实现的接口

1.创建会话id

传入“发送id”和“接收id”给服务端,服务端去创建“会话id”

比如

get请求:http://xxxx:8110/picasso/createSession?fromUserId=1&toUserId=2

返回seesionId,也就是会话id

{
  "code": 200,
  "data": {
    "seesionId": 13
  }
}

2.获取消息记录

 get请求:http://xxxx:8110/picasso/msgList?sessionId=13

返回如下:我让后台按这个来写的,虽然后端表示接口很丑陋~

这个返回的有个要求,就是日期要升序,第一页要显示最新的15条,这个应该能理解吧,聊天框里面最新的日期都是要显示在最下面的,当然,也可以安卓端手动再排序:

Collections.reverse(msgChat1);
{
  "code": 200,
  "data": {
    "total": 30,
    "current": 2,
    "pages": 2,
    "size": 15,
    "list": [
      {
        "id": 195,
        "fromUserId": 2,
        "fromUserName": "青山不改",
        "toUserId": 1,
        "toUserName": "绿水长流",
        "content": "123123",
        "createTime": "2023-03-30 20:42:21",
        "unReadFlag": 1
      },
      {
        "id": 196,
        "fromUserId": 1,
        "fromUserName": "青山不改",
        "toUserId": 2,
        "toUserName": "绿水长流",
        "content": "rqwewqeqw",
        "createTime": "2023-03-30 20:42:26",
        "unReadFlag": 0
      },
      {
        "id": 197,
        "fromUserId": 2,
        "fromUserName": "青山不改",
        "toUserId": 1,
        "toUserName": "绿水长流",
        "content": "rwerew",
        "createTime": "2023-03-30 20:43:01",
        "unReadFlag": 1
      },
      {
        "id": 198,
        "fromUserId": 1,
        "fromUserName": "青山不改",
        "toUserId": 2,
        "toUserName": "绿水长流",
        "content": "1---2",
        "createTime": "2023-03-30 20:43:11",
        "unReadFlag": 0
      },
      {
        "id": 199,
        "fromUserId": 2,
        "fromUserName": "青山不改",
        "toUserId": 1,
        "toUserName": "绿水长流",
        "content": "2----1",
        "createTime": "2023-03-30 20:43:21",
        "unReadFlag": 1
      },
      {
        "id": 200,
        "fromUserId": 2,
        "fromUserName": "青山不改",
        "toUserId": 1,
        "toUserName": "绿水长流",
        "content": "23123",
        "createTime": "2023-03-30 20:52:27",
        "unReadFlag": 1
      },
      {
        "id": 201,
        "fromUserId": 1,
        "fromUserName": "青山不改",
        "toUserId": 2,
        "toUserName": "绿水长流",
        "content": "333",
        "createTime": "2023-03-30 20:52:31",
        "unReadFlag": 0
      },
      {
        "id": 202,
        "fromUserId": 2,
        "fromUserName": "青山不改",
        "toUserId": 1,
        "toUserName": "绿水长流",
        "content": "333",
        "createTime": "2023-03-30 21:02:27",
        "unReadFlag": 1
      },
      {
        "id": 203,
        "fromUserId": 1,
        "fromUserName": "青山不改",
        "toUserId": 2,
        "toUserName": "绿水长流",
        "content": "444",
        "createTime": "2023-03-30 21:02:42",
        "unReadFlag": 0
      },
      {
        "id": 204,
        "fromUserId": 2,
        "fromUserName": "青山不改",
        "toUserId": 1,
        "toUserName": "绿水长流",
        "content": "22",
        "createTime": "2023-03-30 21:03:57",
        "unReadFlag": 1
      },
      {
        "id": 205,
        "fromUserId": 1,
        "fromUserName": "青山不改",
        "toUserId": 2,
        "toUserName": "绿水长流",
        "content": "uqwe",
        "createTime": "2023-03-30 21:05:36",
        "unReadFlag": 0
      },
      {
        "id": 206,
        "fromUserId": 1,
        "fromUserName": "青山不改",
        "toUserId": 2,
        "toUserName": "绿水长流",
        "content": "yyy",
        "createTime": "2023-03-30 21:07:46",
        "unReadFlag": 0
      },
      {
        "id": 207,
        "fromUserId": 2,
        "fromUserName": "青山不改",
        "toUserId": 1,
        "toUserName": "绿水长流",
        "content": "2---1",
        "createTime": "2023-03-30 21:12:28",
        "unReadFlag": 1
      },
      {
        "id": 208,
        "fromUserId": 2,
        "fromUserName": "青山不改",
        "toUserId": 1,
        "toUserName": "绿水长流",
        "content": "2----1",
        "createTime": "2023-03-30 21:12:35",
        "unReadFlag": 1
      },
      {
        "id": 209,
        "fromUserId": 1,
        "fromUserName": "青山不改",
        "toUserId": 2,
        "toUserName": "绿水长流",
        "content": "1----2",
        "createTime": "2023-03-30 21:12:59",
        "unReadFlag": 0
      }
    ]
  }
}

 3.websocket,让后端按下面这个要求让客户端进行连接的,具体怎么搓的我也不清楚,好像是消息之间的转发

ws://"+IP+":8110/websocket/发送id/会话id

 2.APP安卓端

因为安卓端是我写的,所以~代码我可以尽量贴的完整一点

1.聊天的布局:

我这个写法比较简单,就直接将对方的消息和我的消息写在一个item里面了,对比一下数据是不是自己发的,是的话就显示自己的消息ui,不是就隐藏自己的ui

 代码布局如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="right"
    android:orientation="vertical">

    <TextView
        android:id="@+id/tv_time"
        android:gravity="center"
        android:textSize="8dp"
        android:text="2022-10-22 12:00"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

    <LinearLayout
        android:id="@+id/ll_left"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:gravity="left"
        >

        <TextView
            android:visibility="gone"
            android:id="@+id/to_user_id"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
        <TextView
            android:id="@+id/to_user_head"
            android:layout_margin="10dp"
            android:background="@drawable/ic_head_rec_bg"
            android:text="LL"
            android:gravity="center"
            android:textColor="@color/white"
            android:layout_width="50dp"
            android:layout_height="50dp"/>
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:layout_marginRight="50dp"
            android:layout_marginLeft="5dp"
            android:layout_marginTop="16dp"
            android:gravity="left">
            <TextView
                android:visibility="gone"
                android:id="@+id/to_user_name"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textColor="#cccccc"
                android:textStyle="bold"
                android:textSize="12sp"
                android:text="demo_9999"
                />
            <TextView
                android:id="@+id/to_user_msg"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textColor="#000000"
                android:textSize="14sp"
                android:gravity="left"
                android:paddingTop="6dp"
                android:paddingBottom="7dp"
                android:paddingRight="10dp"
                android:paddingLeft="10dp"
                android:background="@drawable/bg_popo_left"
                android:text="mmm"
                />
        </LinearLayout>


    </LinearLayout>




    <LinearLayout
        android:id="@+id/ll_right"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:gravity="right"
        >
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:layout_marginTop="16dp"
            android:layout_marginRight="5dp"
            android:layout_marginLeft="50dp"
            android:gravity="right">
            <TextView
                android:visibility="gone"
                android:id="@+id/from_user_name"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textColor="#cccccc"
                android:textStyle="bold"
                android:textSize="12sp"
                android:text="demo_9999"
                />
            <TextView
                android:id="@+id/from_user_msg"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textColor="@color/theme"
                android:textSize="14sp"
                android:layout_marginBottom="5dp"
                android:paddingTop="6dp"
                android:paddingBottom="7dp"
                android:paddingRight="10dp"
                android:paddingLeft="10dp"
                android:background="@drawable/bg_popo_right"
                android:text="毕加索"
                />
        </LinearLayout>

        <TextView
            android:id="@+id/from_user_head"
            android:layout_margin="10dp"
            android:background="@drawable/ic_head_bg"
            android:text="LL"
            android:gravity="center"
            android:textColor="@color/white"
            android:layout_width="50dp"
            android:layout_height="50dp"/>

        <TextView
            android:id="@+id/from_user_id"
            android:visibility="gone"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>

    </LinearLayout>

</LinearLayout>

2.消息框的总布局

 这个没啥重要的,只是我额外用了一个自定义的listview,这个是用来监听下拉的,毕竟会有历史消息,下拉就直接加载上一页了

布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_marginTop="-25dp"
    android:background="#F6F6F6"
    android:fitsSystemWindows="true"
    android:orientation="vertical">

    <com.picasso.module.ui.CustomRefreshListView
        android:id="@+id/msg_list"
        android:layout_weight="1"
        android:divider="#00000000"
        android:scrollbars="none"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#ffffff"
        android:elevation="5dp"
        android:gravity="center_vertical"
        android:orientation="horizontal"
        android:paddingBottom="10dp">

        <EditText
            android:id="@+id/id_input"
            android:layout_width="0dp"
            android:layout_height="40dp"
            android:layout_marginLeft="15dp"
            android:layout_marginTop="5dp"
            android:layout_marginBottom="5dp"
            android:layout_weight="1"
            android:background="@drawable/bg_round_corner_grey"
            android:gravity="center_vertical"
            android:hint="来聊吧~"
            android:paddingLeft="15dp"
            android:textSize="12dp" />

        <RelativeLayout
            android:layout_width="75dp"
            android:layout_height="40dp"
            android:layout_marginLeft="15dp"
            android:layout_marginRight="15dp"
            android:background="@drawable/bg_btn_orange_back">

            <TextView
                android:id="@+id/send_btn"
                android:layout_width="match_parent"
                android:layout_height="40dp"
                android:gravity="center"
                android:text="发送"
                android:textColor="#ffffff"
                android:textSize="16sp" />
        </RelativeLayout>
    </LinearLayout>
</LinearLayout>

支持下拉的listview控件


public class CustomRefreshListView extends ListView implements AbsListView.OnScrollListener {

    /**
     * 头布局
     */
    private View headerView;

    /**
     * 头部布局的高度
     */
    private int headerViewHeight;

    /**
     * 头部旋转的图片
     */
    private ImageView iv_arrow;

    /**
     * 头部下拉刷新时状态的描述
     */
    private TextView tv_state;

    /**
     * 下拉刷新时间的显示控件
     */
    private TextView tv_time;


    /**
     * 底部布局
     */
    private View footerView;

    /**
     * 底部旋转progressbar
     */
    private ProgressBar pb_rotate;


    /**
     * 底部布局的高度
     */
    private int footerViewHeight;


    /**
     * 按下时的Y坐标
     */
    private int downY;

    private final int PULL_REFRESH = 0;//下拉刷新的状态
    private final int RELEASE_REFRESH = 1;//松开刷新的状态
    private final int REFRESHING = 2;//正在刷新的状态

    /**
     * 当前下拉刷新处于的状态
     */
    private int currentState = PULL_REFRESH;

    /**
     * 头部布局在下拉刷新改变时,图标的动画
     */
    private RotateAnimation upAnimation,downAnimation;

    /**
     * 当前是否在加载数据
     */
    private boolean isLoadingMore = false;

    public CustomRefreshListView(Context context) {
        this(context,null);
    }

    public CustomRefreshListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init(){
        //设置滑动监听
        setOnScrollListener(this);
        //初始化头布局
        initHeaderView();
        //初始化头布局中图标的旋转动画
        initRotateAnimation();
        //初始化为尾布局
        initFooterView();
    }


    /**
     * 初始化headerView
     */
    private void initHeaderView() {
        headerView = View.inflate(getContext(), R.layout.head_custom_listview, null);
        iv_arrow = (ImageView) headerView.findViewById(R.id.iv_arrow);
        pb_rotate = (ProgressBar) headerView.findViewById(R.id.pb_rotate);
        tv_state = (TextView) headerView.findViewById(R.id.tv_state);
        tv_time = (TextView) headerView.findViewById(R.id.tv_time);

        //测量headView的高度
        headerView.measure(0, 0);
        //获取高度,并保存
        headerViewHeight = headerView.getMeasuredHeight();
        //设置paddingTop = -headerViewHeight;这样,该控件被隐藏
        headerView.setPadding(0, -headerViewHeight, 0, 0);
        //添加头布局
        addHeaderView(headerView);
    }

    /**
     * 初始化旋转动画
     */
    private void initRotateAnimation() {

        upAnimation = new RotateAnimation(0, -180, 
                RotateAnimation.RELATIVE_TO_SELF, 0.5f,
                RotateAnimation.RELATIVE_TO_SELF, 0.5f);
        upAnimation.setDuration(300);
        upAnimation.setFillAfter(true);

        downAnimation = new RotateAnimation(-180, -360, 
                RotateAnimation.RELATIVE_TO_SELF, 0.5f,
                RotateAnimation.RELATIVE_TO_SELF, 0.5f);
        downAnimation.setDuration(300);
        downAnimation.setFillAfter(true);
    }

    //初始化底布局,与头布局同理
    private void initFooterView() {
        footerView = View.inflate(getContext(), R.layout.foot_custom_listview, null);
        footerView.measure(0, 0);
        footerViewHeight = footerView.getMeasuredHeight();
        footerView.setPadding(0, -footerViewHeight, 0, 0);
        addFooterView(footerView);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            //获取按下时y坐标
            downY = (int) ev.getY();
            break;
        case MotionEvent.ACTION_MOVE:

            if(currentState==REFRESHING){
                //如果当前处在滑动状态,则不做处理
                break;
            }
            //手指滑动偏移量
            int deltaY = (int) (ev.getY() - downY);

            //获取新的padding值
            int paddingTop = -headerViewHeight + deltaY;
            if(paddingTop>-headerViewHeight && getFirstVisiblePosition()==0){
                //向下滑,且处于顶部,设置padding值,该方法实现了顶布局慢慢滑动显现
                headerView.setPadding(0, paddingTop, 0, 0);

                if(paddingTop>=0 && currentState==PULL_REFRESH){
                    //从下拉刷新进入松开刷新状态
                    currentState = RELEASE_REFRESH;
                    //刷新头布局
                    refreshHeaderView();
                }else if (paddingTop<0 && currentState==RELEASE_REFRESH) {
                    //进入下拉刷新状态
                    currentState = PULL_REFRESH;
                    refreshHeaderView();
                }


                return true;//拦截TouchMove,不让listview处理该次move事件,会造成listview无法滑动
            }


            break;
        case MotionEvent.ACTION_UP:
            if(currentState==PULL_REFRESH){
                //仍处于下拉刷新状态,未滑动一定距离,不加载数据,隐藏headView
                headerView.setPadding(0, -headerViewHeight, 0, 0);
            }else if (currentState==RELEASE_REFRESH) {
                //滑倒一定距离,显示无padding值得headcView
                headerView.setPadding(0, 0, 0, 0);
                //设置状态为刷新
                currentState = REFRESHING;

                //刷新头部布局
                refreshHeaderView();

                if(listener!=null){
                    //接口回调加载数据
                    listener.onPullRefresh();
                }
            }
            break;
        }
        return super.onTouchEvent(ev);
    }

    /**
     * 根据currentState来更新headerView
     */
    private void refreshHeaderView(){
        switch (currentState) {
        case PULL_REFRESH:
            tv_state.setText("下拉刷新");
            iv_arrow.startAnimation(downAnimation);
            break;
        case RELEASE_REFRESH:
           // tv_state.setText("松开刷新");
            iv_arrow.startAnimation(upAnimation);
            break;
        case REFRESHING:
            iv_arrow.clearAnimation();//因为向上的旋转动画有可能没有执行完
            iv_arrow.setVisibility(View.INVISIBLE);
            pb_rotate.setVisibility(View.VISIBLE);
           // tv_state.setText("正在刷新...");
            break;
        }
    }

    /**
     * 完成刷新操作,重置状态,在你获取完数据并更新完adater之后,去在UI线程中调用该方法
     */
    public void completeRefresh(){
        if(isLoadingMore){
            //重置footerView状态
            footerView.setPadding(0, -footerViewHeight, 0, 0);
            isLoadingMore = false;
        }else {
            //重置headerView状态
            headerView.setPadding(0, -headerViewHeight, 0, 0);
            currentState = PULL_REFRESH;
            pb_rotate.setVisibility(View.INVISIBLE);
            iv_arrow.setVisibility(View.VISIBLE);
            tv_state.setText("下拉刷新");
            tv_time.setText("最后刷新:"+getCurrentTime());
        }
    }

    /**
     * 获取当前系统时间,并格式化
     * @return
     */
    private String getCurrentTime(){
        SimpleDateFormat format = new SimpleDateFormat("yy-MM-dd HH:mm:ss");
        return format.format(new Date());
    }

    private OnRefreshListener listener;
    public void setOnRefreshListener(OnRefreshListener listener){
        this.listener = listener;
    }
    public interface OnRefreshListener{
        void onPullRefresh();
        void onLoadingMore();
    }

    /**
     * SCROLL_STATE_IDLE:闲置状态,就是手指松开
     * SCROLL_STATE_TOUCH_SCROLL:手指触摸滑动,就是按着来滑动
     * SCROLL_STATE_FLING:快速滑动后松开
     */
    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        if(scrollState==OnScrollListener.SCROLL_STATE_IDLE 
                && getLastVisiblePosition()==(getCount()-1) &&!isLoadingMore){
            isLoadingMore = true;

            footerView.setPadding(0, 0, 0, 0);//显示出footerView
            setSelection(getCount());//让listview最后一条显示出来,在页面完全显示出底布局

            if(listener!=null){
                listener.onLoadingMore();
            }
        }
    }


    @Override
    public void onScroll(AbsListView view, int firstVisibleItem,
            int visibleItemCount, int totalItemCount) {
    }

里面有些控件之类的,可以自己画画补充,我是随便放的

head_custom_listview.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <ProgressBar
        android:id="@+id/pb_rotate"
        android:layout_width="match_parent"
        android:layout_height="20dp"/>
    <TextView
        android:id="@+id/tv_state"
        android:layout_width="match_parent"
        android:layout_height="20dp"/>
    <TextView
        android:id="@+id/tv_time"
        android:layout_width="match_parent"
        android:layout_height="20dp"/>
    <ImageView
        android:id="@+id/iv_arrow"
        android:layout_width="match_parent"
        android:layout_height="20dp"/>

</LinearLayout>

3.websocket代码

记住要在清单文件里面添加这一行

        <service
            android:name=".websocket.JWebSocketClientService1"
            android:enabled="true"
            android:exported="true" />
下面是我百度来的,改一改可以直接用
JWebSocketClientService1
public class JWebSocketClientService1 extends Service {
    private String TAG="JWebSocketClientService";
    private URI uri;
    public JWebSocketClient client;
    private JWebSocketClientBinder mBinder = new JWebSocketClientBinder();

    //用于Activity和service通讯
    public class JWebSocketClientBinder extends Binder {
        public JWebSocketClientService1 getService() {
            return JWebSocketClientService1.this;
        }
    }
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }


    @Override
    public void onCreate() {
        super.onCreate();

        //初始化websocket
        initSocketClient();
        mHandler.postDelayed(heartBeatRunnable, HEART_BEAT_RATE);//开启心跳检测
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return super.onStartCommand(intent, flags, startId);
    }


    /**
     * 初始化websocket连接
     */
    private void initSocketClient() {
        URI uri = URI.create(Constant.webSocketHost+"/"+Constant.SendId+"/"+Constant.ChatId);//测试使用
        //Log.e("initSocketClient",Constant.webSocketHost+"/"+Constant.SendId+"/"+Constant.ChatId);
        Log.e("SocketInfo-host",Constant.webSocketHost+"/"+Constant.SendId+"/"+Constant.ChatId);
        client = new JWebSocketClient(uri) {
            @Override
            public void onMessage(String message) {
                Log.e("JWebSocketClientService", "收到的消息:" + message);

                Intent intent = new Intent();//广播接收到的消息,在Activity接收
                intent.setAction("com.picasso.shanghai.activity.ChatActivity$ChatMessageReceiver");
                intent.putExtra("message", message);
                sendBroadcast(intent);
            }

            @Override
            public void onOpen(ServerHandshake handshakedata) {
                super.onOpen(handshakedata);
                Log.e("JWebSocketClientService", "websocket连接成功");
            }
        };
        connect();
    }
    /**
     * 连接websocket
     */
    private void connect() {
        new Thread() {
            @Override
            public void run() {
                try {
                    //connectBlocking多出一个等待操作,会先连接再发送,否则未连接发送会报错
                    client.connectBlocking();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }.start();

    }
    /**
     * 发送消息
     *
     * @param msg
     */
    public void sendMsg(String msg) {
        if (null != client) {
            Log.e("JWebSocketClientService", "发送的消息:" + msg);
            client.send(msg);
        }else {

        }
    }

    //    -------------------------------------websocket心跳检测------------------------------------------------
    private static final long HEART_BEAT_RATE = 30 * 1000;//每隔30秒进行一次对长连接的心跳检测
    private Handler mHandler = new Handler();
    private Runnable heartBeatRunnable = new Runnable() {
        @Override
        public void run() {

            Log.e("JWebSocketClientService", "心跳包检测websocket连接状态");
            if (client != null) {
                if (client.isClosed()) {
                    reconnectWs();
                }else {
                    Log.e("JWebSocketClientService", "!client.isClosed()");
                    //业务逻辑 这里如果服务端需要心跳包为了防止断开 需要不断发送消息给服务端
                    // client.send("");
                }
            } else {
                //如果client已为空,重新初始化连接
                client = null;
                Log.e("JWebSocketClientService", "client = null");
                initSocketClient();
            }
            //每隔一定的时间,对长连接进行一次心跳检测
            mHandler.postDelayed(this, HEART_BEAT_RATE);
        }
    };
    /**
     * 开启重连
     */
    private void reconnectWs() {
        mHandler.removeCallbacks(heartBeatRunnable);
        new Thread() {
            @Override
            public void run() {
                try {
                    Log.e("JWebSocketClientService", "开启重连");
                    client.reconnectBlocking();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }

    @Override
    public void onDestroy() {
        Log.d(TAG, "onDestroy: ....");
        super.onDestroy();
    }
JWebSocketClient
public class JWebSocketClient extends WebSocketClient {
    public JWebSocketClient(URI serverUri) {
        super(serverUri, new Draft_6455());
    }

    @Override
    public void onOpen(ServerHandshake handshakedata) {
        Log.e("JWebSocketClient", "onOpen()");
    }

    @Override
    public void onMessage(String message) {
        Log.e("JWebSocketClient", "onMessage()");
    }

    @Override
    public void onClose(int code, String reason, boolean remote) {
        Log.e("JWebSocketClient", "onClose()");
    }

    @Override
    public void onError(Exception ex) {
        Log.e("JWebSocketClient", "onError()");
    }
}

4.实体类,和适配器

MsgChat.java

public class MsgChat {

    private int fromUserId,toUserId,unReadFlag;
    private String fromUserName,toUserName,createTime,content;


    public int getFromUserId() {
        return fromUserId;
    }

    public void setFromUserId(int fromUserId) {
        this.fromUserId = fromUserId;
    }

    public int getToUserId() {
        return toUserId;
    }

    public void setToUserId(int toUserId) {
        this.toUserId = toUserId;
    }

    public String getCreateTime() {
        return createTime;
    }

    public void setCreateTime(String createTime) {
        this.createTime = createTime;
    }

    public int getUnReadFlag() {
        return unReadFlag;
    }

    public void setUnReadFlag(int unReadFlag) {
        this.unReadFlag = unReadFlag;
    }

    public String getFromUserName() {
        return fromUserName;
    }

    public void setFromUserName(String fromUserName) {
        this.fromUserName = fromUserName;
    }

    public String getToUserName() {
        return toUserName;
    }

    public void setToUserName(String toUserName) {
        this.toUserName = toUserName;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }
}

MessageAdapter.java

public class MessageAdapter extends BaseAdapter implements ListAdapter {

    private ArrayList<MsgChat> msgChatArrayList;
    private int id;
    private Context mcontext;
    private LayoutInflater inflater;


    public MessageAdapter(int sub_item, Context mcontext, ArrayList<MsgChat> msgChatArrayList) {
        this.msgChatArrayList = msgChatArrayList;
        this.mcontext = mcontext;
        this.id = sub_item;
        inflater = LayoutInflater.from(mcontext);
    }

    @Override
    public int getCount() {
        return msgChatArrayList.size();
    }

    @Override
    public Object getItem(int i) {
        return msgChatArrayList.get(i);
    }

    @Override
    public long getItemId(int i) {
        return i;
    }


    public void addItem(MsgChat msgChat, ArrayList<MsgChat> msgChats) {

        msgChats.add(msgChat);
        this.msgChatArrayList = msgChats;
        this.notifyDataSetChanged();
    }




    @SuppressLint("WrongConstant")
    @Override
    public View getView(int position, View view, ViewGroup viewGroup) {

        LinearLayout ll_left = null;
        LinearLayout ll_right = null;

        TextView tv_time = null;

        TextView from_user_id = null;
        TextView from_user_name = null;
        TextView from_user_head = null;
        TextView from_user_msg = null;

        TextView to_user_id = null;
        TextView to_user_name = null;
        TextView to_user_head = null;
        TextView to_user_msg = null;

        ViewHolder viewHolder;
        if (view == null) {
            //获取item视图类型
            view = inflater.inflate(id, null);
            ll_left = (LinearLayout) view.findViewById(R.id.ll_left);
            ll_right = (LinearLayout) view.findViewById(R.id.ll_right);

            tv_time = (TextView) view.findViewById(R.id.tv_time);

            from_user_id = (TextView) view.findViewById(R.id.from_user_id);
            from_user_name = (TextView) view.findViewById(R.id.from_user_name);
            from_user_head = (TextView) view.findViewById(R.id.from_user_head);
            from_user_msg = (TextView) view.findViewById(R.id.from_user_msg);

            to_user_id = (TextView) view.findViewById(R.id.to_user_id);
            to_user_name = (TextView) view.findViewById(R.id.to_user_name);
            to_user_head = (TextView) view.findViewById(R.id.to_user_head);
            to_user_msg = (TextView) view.findViewById(R.id.to_user_msg);

            view.setTag(new ViewHolder(ll_left,ll_right,tv_time,from_user_id,from_user_head,from_user_name,from_user_msg,to_user_id,to_user_head,to_user_name,to_user_msg));
        } else {
            ViewHolder viewHolder1 = (ViewHolder) view.getTag(); // 重新获取ViewHolder
            ll_left = viewHolder1.ll_left;
            ll_right = viewHolder1.ll_right;

            tv_time = viewHolder1.tv_time;

            from_user_id = viewHolder1.from_user_id;
            from_user_name = viewHolder1.from_user_name;
            from_user_head = viewHolder1.from_user_head;
            from_user_msg = viewHolder1.from_user_msg;

            to_user_id = viewHolder1.to_user_id;
            to_user_name = viewHolder1.to_user_name;
            to_user_head = viewHolder1.to_user_head;
            to_user_msg = viewHolder1.to_user_msg;
        }

        MsgChat msgChat = (MsgChat) msgChatArrayList.get(position);
        from_user_id.setText(msgChat.getFromUserId()+"");
        if(msgChat.getFromUserId()==Constant.SendId){
            ll_left.setVisibility(View.GONE);
            ll_right.setVisibility(View.VISIBLE);
        }else {
            ll_right.setVisibility(View.GONE);
            ll_left.setVisibility(View.VISIBLE);
        }


        try {
            from_user_name.setText(msgChat.getFromUserName().substring(0, 2));
        }catch(Exception e){
            from_user_name.setText("NULL");
        }

        try {
            from_user_head.setText(msgChat.getFromUserName().substring(0, 2));
        }catch(Exception e){
            from_user_head.setText("NULL");
        }
        from_user_msg.setText(msgChat.getContent()+"");

        to_user_id.setText(msgChat.getToUserId()+"");
        try {
            to_user_name.setText(msgChat.getToUserName().substring(0, 2));
        }catch(Exception e){
            to_user_name.setText("NULL");
        }

        try {
            to_user_head.setText(msgChat.getToUserName().substring(0, 2));
        }catch(Exception e){
            to_user_head.setText("NULL");
        }
        to_user_msg.setText(msgChat.getContent()+"");


        tv_time.setText(msgChat.getCreateTime());



        return view;
    }
    private final class ViewHolder {

        LinearLayout ll_left = null;
        LinearLayout ll_right = null;

        TextView tv_time = null;

        TextView from_user_id = null;
        TextView from_user_name = null;
        TextView from_user_head = null;
        TextView from_user_msg = null;

        TextView to_user_id = null;
        TextView to_user_name = null;
        TextView to_user_head = null;
        TextView to_user_msg = null;

        public ViewHolder(LinearLayout ll_left,LinearLayout ll_right,TextView tv_time,TextView from_user_id,TextView from_user_head,TextView from_user_name,TextView from_user_msg,TextView to_user_id,TextView to_user_head,TextView to_user_name,TextView to_user_msg) {
            this.ll_left = ll_left;
            this.ll_right = ll_right;

            this.tv_time = tv_time;

            this.from_user_id = from_user_id;
            this.from_user_name = from_user_name;
            this.from_user_head = from_user_head;
            this.from_user_msg = from_user_msg;

            this.to_user_id = to_user_id;
            this.to_user_name = to_user_name;
            this.to_user_head = to_user_head;
            this.to_user_msg = to_user_msg;
        }


    }

5.ChatActivity核心代码

public class ChatActivity extends BaseActivity {
    private JWebSocketClientService1.JWebSocketClientBinder binder;
    private JWebSocketClientService1 jWebSClientService;
    private JWebSocketClient client;
    private ChatMessageReceiver chatMessageReceiver;
    private Intent intent;
    private TextView send_btn;
    private EditText id_input;
    private CustomRefreshListView msg_list;
    private ArrayList<MsgChat> msgChats = new ArrayList<>();
    private MsgChat msgChat;
    private MessageAdapter messageAdapter;
    private int PAGES = 0;
    private int CURRENT = 0;
    private boolean StateY = false;//true 下拉,false 上拉


    private class ChatMessageReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            String message=intent.getStringExtra("message");
            Log.e("SocketInfo-Receiver",message);
            MsgChat msgChat = new MsgChat();
            JSONObject msg = JSONObject.parseObject(message);
            msgChat.setFromUserId(msg.getInteger("fromUserId"));
            msgChat.setFromUserName(msg.getString("fromUserName"));
            msgChat.setToUserId(msg.getInteger("toUserId"));
            msgChat.setToUserName(msg.getString("toUserName"));
            msgChat.setContent(msg.getString("content"));
            msgChat.setCreateTime(msg.getString("createTime"));
            msgChat.setUnReadFlag(msg.getInteger("unReadFlag"));
            Log.e("====>>", Constant.SendId+"----"+msg.getInteger("fromUserId"));
            Log.e("====>>", msg.getString("content"));
            messageAdapter.addItem(msgChat,msgChats);
            msg_list.setSelection(messageAdapter.getCount()-1);
        }
    }

    private void doRegisterReceiver() {
        Log.e("ChatMessageReceiver", "广播的权限1");
        chatMessageReceiver = new ChatMessageReceiver();
        IntentFilter filter = new IntentFilter("com.picasso.shanghai.activity.ChatActivity$ChatMessageReceiver");
        registerReceiver(chatMessageReceiver, filter);
    }

    private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            Log.e("this", "服务与活动成功绑定");
            binder = (JWebSocketClientService1.JWebSocketClientBinder) iBinder;
            jWebSClientService = binder.getService();
            client = jWebSClientService.client;
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            Log.e("this", "服务与活动成功断开");
        }
    };

    @Override
    public int getLayout() {
        return R.layout.activity_chat;
    }

    @Override
    public void initView() {

        setStatusBar();

        //启动服务
        startJWebSClientService();

        //绑定服务
        bindService();

        //注册广播
        doRegisterReceiver();

        //初始化历史消息
        initHistoryMsg(Constant.ChatId,"");

        msg_list = findViewById(R.id.msg_list);
        msg_list.setOnRefreshListener(new CustomRefreshListView.OnRefreshListener() {
            @Override
            public void onPullRefresh() {
                if(CURRENT!=PAGES){
                    CURRENT++;
                    Log.e("list","xia"+CURRENT);
                    StateY = true;
                    initHistoryMsg(Constant.ChatId,String.valueOf(CURRENT));
                }
                msg_list.completeRefresh();
            }

            @Override
            public void onLoadingMore() {
                msg_list.completeRefresh();
                StateY = false;
                Log.e("list","shang");
            }
        });
       // msg_list.setLayoutManager(new LinearLayoutManager(this));
        send_btn = findViewById(R.id.send_btn);
        id_input = findViewById(R.id.id_input);
        send_btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                jWebSClientService.sendMsg(id_input.getText().toString());
                id_input.setText("");
            }
        });
    }

    private Handler Thandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case 1:
                    messageAdapter = new MessageAdapter(R.layout.item_chat_msg_list,ChatActivity.this,msgChats);
                    msg_list.setAdapter(messageAdapter);
                    if(StateY){
                        //msg_list.setSelection(0);
                    }else {
                        msg_list.setSelection(messageAdapter.getCount()-1);
                    }
                    break;
            }
        }
    };



    //初始化历史消息
    private void initHistoryMsg(int chatId,String pageNum) {
        ChatListener chatListener = new ChatListener();
        chatListener.historyMessage(chatId,pageNum, new RequestCallback() {
            @Override
            public void success(String str) {

                JSONObject data_key5 = JSONObject.parseObject(str);
                String _data = data_key5.getString("data");
                JSONObject data_key6 = JSONObject.parseObject(_data);
                PAGES = data_key6.getInteger("pages");
                CURRENT = data_key6.getInteger("current");
                Log.e("list",PAGES+"");

                try {
                    String list = data_key6.getString("list");
                    Log.e("list",list);
                    List<MsgChat> msgChat1 = JSON.parseArray(list, MsgChat.class);

                    //数据翻转一下
                    if(StateY){
                        Collections.reverse(msgChat1);
                    }

                    for (MsgChat pp : msgChat1) {
                        msgChat = new MsgChat();
                        int fromUserId = pp.getFromUserId();
                        String fromUserName = pp.getFromUserName();
                        int toUserId = pp.getToUserId();
                        String toUserName = pp.getToUserName();
                        String createTime = pp.getCreateTime();
                        String content = pp.getContent();
                        int unReadFlag = pp.getUnReadFlag();
                        msgChat.setFromUserId(fromUserId);
                        msgChat.setFromUserName(fromUserName);
                        msgChat.setToUserId(toUserId);
                        msgChat.setToUserName(toUserName);
                        msgChat.setContent(content);
                        msgChat.setCreateTime(createTime);
                        msgChat.setUnReadFlag(unReadFlag);
                        if(StateY){
                            msgChats.add(0,msgChat);
                        }else {
                            msgChats.add(msgChat);
                        }


                    }
                }catch(Exception e){
                    Log.e("list","数据为空");
                }
                Message msg = new Message();
                msg.what = 1;
                Thandler.sendMessage(msg);
            }
            @Override
            public void error(String error) {
                Message msg = new Message();
                msg.what = 1;
                Thandler.sendMessage(msg);
            }
        });
    }





    private void startJWebSClientService() {
        Log.e("this", "开启服务");
        intent = new Intent(ChatActivity.this, JWebSocketClientService1.class);
        startService(intent);
    }

    private void restartJWebSClientService() {
        //销毁服务
        Log.e("this", "销毁服务");
        stopService(intent);
    }


    /**
     * 绑定服务
     */
    private void bindService() {
        Intent bindIntent = new Intent(ChatActivity.this, JWebSocketClientService1.class);
        bindService(bindIntent, serviceConnection, 0000);
    }


    /**
     * 注销广播
     */
    private void unRegisterReceiver() {
        unregisterReceiver(chatMessageReceiver);
    }

    //设置状态栏为透明
    protected void setStatusBar() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        unRegisterReceiver();
        unbindService(serviceConnection);
        restartJWebSClientService();
    }

    @Override
    public void initData() {

    }



}

6.常量参数文件

这个值得一说的,跳转之前我把房间号和sendId存在这个文件,比对的时候就直接拿这个文件里面的sendId和fromUserId进行比对,用来区分是自己发送的,还是对方发送的

Constant.java
public class Constant {

    public final static String IP = "192.168.1.104";
    public final static String webSocketHost = "ws://"+IP+":8110/websocket";
    public final static String Host = "http://"+IP+":8110/";
    public final static String get_picasso_createSession = Host + "picasso/createSession";
    public final static String get_picasso_msgList= Host + "picasso/msgList?sessionId=";

    public static int ChatId = 0;
    public static int SendId = 0;

}

7.http请求的回调

写的很糙,不看也罢

public class ChatListener {


    private String TAG = "RequestListener";
    private RequestCallback requestListener;


    //获取历史消息
    public void historyMessage(int chatId,String pageNum, RequestCallback requestListener) {
        this.requestListener = requestListener;
        HistoryMessageRequest(Constant.get_picasso_msgList,chatId,pageNum);
    }


    //创建会话
    public void createSession(int from_id,int to_id, RequestCallback requestListener) {
        this.requestListener = requestListener;
        CreateSessionRequest(Constant.get_picasso_createSession,from_id,to_id);
    }

    public void CreateSessionRequest(String str,int from_id,int to_id){
        OkHttpClient client = new OkHttpClient();
        Request request1 = new Request.Builder()
                .url(str+"?fromUserId="+from_id+"&toUserId="+to_id)
                .build();

        client.newCall(request1).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                Log.e("CreateSessionRequest==>",e.getMessage());
                requestListener.success(e.getMessage());
            }
            @Override
            public void onResponse(Call call, Response response) throws IOException {
                    String result = response.body().string();
                    requestListener.success(result);
            }
        });
    }



    public void HistoryMessageRequest(String str,int chatId,String pageNum){
        OkHttpClient client = new OkHttpClient();
        Request request1 = new Request.Builder()
                .url(str+chatId+"&page="+pageNum)
                .build();

        client.newCall(request1).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                Log.e("CreateSessionRequest==>",e.getMessage());
                requestListener.success(e.getMessage());
            }
            @Override
            public void onResponse(Call call, Response response) throws IOException {
                String result = response.body().string();
                requestListener.success(result);
            }
        });
    }




}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/451411.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

RabbitMQ防止消息丢失

生产者没有成功把消息发送到MQ 丢失的原因 &#xff1a;因为网络传输的不稳定性&#xff0c;当生产者在向MQ发送消息的过程中&#xff0c;MQ没有成功接收到消息&#xff0c;但是生产者却以为MQ成功接收到了消息&#xff0c;不会再次重复发送该消息&#xff0c;从而导致消息的丢…

直播授课在线课堂学习平台的设计与实现nodejs+vue

在各学校的教学过程中&#xff0c;直播授课管理是一项非常重要的事情。随着计算机多媒体技术的发展和网络的普及&#xff0c;“基于网络的学习模式”正悄无声息的改变着传统的直播学习模式&#xff0c;“基于网络的直播教学平台”的研究和设计也成为教育技术领域的热点课题。1、…

金融数据获取:获取网站交互式图表背后的数据,Headers模拟浏览器请求,防盗链破解及Cookie验证

有时候写爬虫难免会遇上只提供一张图表却没有背后结构化数据的情况&#xff0c;尤其是金融市场数据&#xff0c;例如股票K线&#xff0c;基金净值等。笔者看了市面上主流的行情网站&#xff0c;基本都是图一这样的交互式图表&#xff0c;但这样的图表是没有办法直接拿到原始数据…

前端技术学习第八讲:VUE基础语法---初识VUE

VUE基础语法—初识VUE 一、初识VUE Vue (读音 /vjuː/&#xff0c;类似于 view) 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是&#xff0c;Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层&#xff0c;不仅易于上手&#xff0c;还便于与第三方库…

如何快速白嫖一个SSL证书

首先呢&#xff0c;还是要非常感谢各大云厂商能够为白嫖党免费提供申请SSL证书的这个一个平台。 SSL证书作用不在描述&#xff0c;自行百度&#xff1f; 本篇主要从百度云、腾讯云、阿里云讨论下 阿里云 地址&#xff1a;阿里云-计算&#xff0c;为了无法计算的价值 首先需…

手写axios源码系列三:dispatchRequest发送请求

文章目录 一、dispatchRequest 发送请求代码设计思路1、创建 dispatchRequest.js 文件2、创建 adapters.js 文件3、创建 xhr.js 文件4、总结 上篇文章中介绍了创建 axios 函数对象的思路&#xff0c;在 Axios 的原型对象上声明了一个 request 方法&#xff0c;在 request 方法中…

基于Java+Springboot+vue在线版权登记管理系统设计实现

基于JavaSpringbootvue在线版权登记管理系统设计实现 博主介绍&#xff1a;5年java开发经验&#xff0c;专注Java开发、定制、远程、指导等,csdn特邀作者、专注于Java技术领域 作者主页 超级帅帅吴 Java项目精品实战案例《500套》 欢迎点赞 收藏 ⭐留言 文末获取源码联系方式 文…

亚马逊云科技在上云旅程中不断调整和优化成本管理计划

亚马逊云科技的云财务管理旨在帮助企业建立一个成功的CFM战略&#xff1a;通过4个云财务管理CFM原则或步骤作为路线图&#xff1a;SEE-查看、SAVE-保存、PLAN-计划和RUN-运行。 对现有工作负载的预测和规划 1、 优化计算资源与架构&#xff1a; 与技术业务相关部门合作&…

Notion打不开

如果Notion打不开&#xff0c;可以尝试以下方法&#xff1a;1. 尝试Ping一下Notion的服务器&#xff0c;如果是正常的&#xff0c;但访问502了&#xff0c;那么很可能是DNS污染了。建议将DNS修改为114.114.114.114&#xff0c;再加个8.8.8.8&#xff0c;修改完成后再度访问Noti…

doccano使用记录

参考文章&#xff1a;https://github.com/PaddlePaddle/PaddleNLP/blob/develop/model_zoo/uie/doccano.md 参考文章&#xff1a;https://github.com/doccano/doccano 参考文章&#xff1a;https://doccano.github.io/doccano/ 参考文章&#xff1a;https://zhuanlan.zhihu.com…

06 | 立迈胜电机使用问题汇总

1 前提 使用STM2832B-485-MA-0FS等 2 常见问题 2.1 操作相关 问题1&#xff1a;怎么识别到电机设备 解决方法&#xff1a; 1、电机上电&#xff0c;在通讯处&#xff0c;点击【打开】 2、设备类型选择【串口】 3、选择串口选择【对应的COM】 4、选择对应的波特率 问题2…

python 的 object 与type的关系

python 的 object 与type的关系 是并列关系&#xff0c;两种是相互依赖的 查询父类 type.__bases__ object.__bases__(<class ‘object’>,) () 查询类型 type(type) type(object)<class ‘type’> <class ‘type’> 在python中&#xff0c;type用于描述…

Docker之Docker网络

Docker网络 1. 理解Docker01.1 测试1.2 原理1.3 小结 2. -link3. 自定义网络3.1 网络模式3.2 测试3.3 自定义网络 4. 网络连通5. 实战&#xff1a;部署Redis集群6. 总结 1. 理解Docker0 清空所有环境 docker rm -f $(docker ps -aq) docker rmi -f $(docker images -aq)1.1 测…

51.现有移动端开源框架及其特点—PocketFlow-1

51.1 简介 全球首个自动模型压缩框架一款面向移动端AI开发者的自动模型压缩框架,集成了当前主流的模型压缩与训练算法,结合自研超参数优化组件实现了全程自动化托管式的模型压缩与加速。 开发者无需了解具体算法细节,即可快速地将AI技术部署到移动端产品上,实现了自动托管式…

Java项目打包exe运行文件

Java项目打包exe运行文件 JavaSE打包成exe运行文件的方法有很多种&#xff0c;此处我们主要讲解我常用的一种exe4j&#xff0c;打包前我们需要先安装exe4j这个工具。 注意&#xff1a;exe4j仅支持最低JDK1.8最高JDK11&#xff0c;所以在安装之前一定要查看自己的JDK版本&#…

银行数字化转型导师坚鹏:数字化时代普惠金融模式和产品创新

数字化时代普惠金融模式和产品创新 课程背景&#xff1a; 很多银行存在以下问题&#xff1a; 不清楚普惠金融的机遇与挑战&#xff1f; 不知道普惠金融模式和产品如何创新&#xff1f; 不知道普惠金融产品创新的成功案例&#xff1f; 课程特色&#xff1a; 用实战案例…

使用Docker安装Zookpeer集群

1&#xff09;需要提前安装python和docker-compose 注&#xff1a;sudo权限看自己机器的权限 安装python-pip&#xff1a;sudo yum -y install epel-releasesudo yum -y install python-pip安装docker-compose&#xff1a;sudo pip install docker-compose 注意在安装过程中很…

FileZilla密钥登录

使用密码登录非常的方便&#xff0c;但是有的客户的云服务器上是限定只能通过密钥登录。我一般使用命令行的scp命令就可以正常上传&#xff0c;但是对于我一些同事来说&#xff0c;就很不方便。 生成密钥 这个不难&#xff0c;可以参考我之前的文章。 《Mac使用ssh连接远程服…

实验07:子集和问题

1.实验目的&#xff1a; 深刻理解回溯法的基本思想&#xff0c;掌握回溯法解决问题的一般步骤&#xff0c;学会使用回溯法解决实际问题.运用所熟悉的编程工具&#xff0c;借助回溯法的思想求解子集和数的问题。 2.实验内容&#xff1a; 给定 n n n 个正整数 { x 1 , x 2 ,…

springboot 接口防刷(根据IP与路径限制)

接口防刷 一、全局接口防刷&#xff08;通过拦截器方式&#xff09;1、原理 代码示例 二、个别接口防刷&#xff08;接口注解方式)1、代码示例 一、全局接口防刷&#xff08;通过拦截器方式&#xff09; 1、原理 代码示例 通过ip地址uri拼接用以作为访问者访问接口区分通过…