夜雨聆风学习资料网

ARTICLE · 1358197

JAVA在线律师咨询在线咨询服务系统源码支持小程序+公众号+H5

JAVA在线律师咨询在线咨询服务系统源码支持小程序+公众号+H5

小程序 · 公众号 · H5

LEGAL CONSULT SYSTEM / JAVA

律师管理 · 咨询管理 · 精准分类 · 认证审核

JAVA在线律师咨询系统源码

支持小程序

把律师、咨询订单与会员权益收进同一套后台

SpringBoot · MyBatisPlus · uni-app · Vue

小程序 · 公众号 · H5  /  律师 · 咨询 · 会员 · 认证

法律咨询最怕「接不上」:用户描述完问题找不到对应领域的律师,律师看不到订单全貌,平台不知道哪一单超时未接——这些环节散落在聊天记录和表格里,出了问题很难追溯。

这套系统做的事,是把「找律师 — 下订单 — 接单 — 咨询 — 结算」串成一条后台可查的链路,用户在小程序、公众号、H5 提问,律师在工作台接单,管理员在后台审认证,各自看到各自那一面。

下面按 9 个功能点拆开写:每一节讲清接什么输入、内部状态怎么变、最后输出什么,再给三端分工和 7 段能直接对照的代码。

OVERVIEW / 功能总览

九个模块,跑通一次在线咨询

不是功能清单,是一条链路:律师建档并通过认证,用户按分类选人下单,律师接单咨询,平台看会员权益与状态流转。

律师管理

律师档案、执业证号、擅长领域统一建档

咨询管理

订单从待接单到已完成全程留痕

个人中心

我的咨询、零钱、资料与认证入口

会员权益

月卡季卡年卡与免费咨询次数

精准分类

婚姻家事、合同债务等领域两级分类

图文咨询

文字加图片描述,留痕可回溯

电话咨询

预约时段与通话记录统一归档

接单状态

在线空闲互斥,防并发抢单

认证管理

执业资质提交与后台审核流转

一套主数据

律师、订单、会员共用一套用户与权限

01律师管理:档案与擅长领域

输入管理员录入律师姓名、执业证号、从业年限、擅长领域与简介,关联平台账号;这些数据落成 t_lawyer 里的一条主档,不是一张名片。

状态律师在「在线 / 离线」和「待审核 / 已通过 / 已驳回」两条状态线上各自流转;领域改动后,首页列表与筛选条件同步更新。

输出一个可被精准分类检索、可被订单引用的律师档案,首页律师卡片直接取用它。

【在线律师咨询】实拍 01|首页分类与律师卡片、律师详情、我的页面与订单入口;截图数据为演示数据

02咨询管理:订单全链路留痕

输入用户选择领域、咨询方式与问题描述,提交后生成带请求号的咨询订单,包含咨询类型、内容与金额。

状态订单在「待接单 → 咨询中 → 已完成 / 已取消」之间流转,每次变更带上 version,状态与时间戳都记在库里。

输出一条可在用户端、律师工作台、管理后台三处查询的订单记录,配合索引按用户或律师筛选。

【在线律师咨询】实拍 02|咨询下单与支付方式、零钱明细、律师工作台接单状态;截图数据为演示数据

03个人中心:一个入口看全量

输入用户登录后进入个人中心,输入是当前账号的 user_id,系统按它聚合咨询、零钱、权益与资料。

状态页面展示累计咨询数、评价数、可提现收益与认证状态;切换用户端 / 律师端时,入口跟着角色变化。

输出一个把「我的订单、我的零钱、我的权益、认证资料」收在一起的页面,减少来回找入口。

04会员权益:次数与有效期

输入用户购买或领取月卡、季卡、年卡,系统写入等级、免费咨询次数与到期时间。

状态下单时按条件扣减免费次数,次数不足或已过期就拒绝并回滚;会员折扣、专属券与生效状态由同一份权益数据驱动。

输出会员页能看到生效中的套餐、剩余天数与已享权益,下单时自动按权益计价。

05精准分类:按领域找对人

输入运营维护两级分类(婚姻家事、合同债务、劳动人事等),律师建档时绑定其中一个领域。

状态分类树只返回启用项,首页图标、筛选下拉与律师列表共用同一套分类 id,不各写一份。

输出用户点一个领域就能筛出对应律师,避免「什么都会」式的模糊匹配。

06图文咨询:描述与图片留痕

输入用户填写问题描述、上传历史材料图片,选择图文咨询并提交,内容随订单一起落库。

状态律师在工作台看到完整描述与图片,回复按时间正序排列;订单完成前双方可见,完成后归档。

输出一条有文字、有附件、有回复链的咨询记录,后续有争议可回溯到原始描述。

07电话咨询:预约与记录归档

输入用户选择电话咨询、填可接通时段与问题摘要,系统生成同样结构的订单,只是 consult_type 不同。

状态接单后进入咨询中,通话结果由律师回填;未接单超时按规则退款,状态回到已取消。

输出和图文咨询共用一张订单表的电话记录,后台能按咨询类型分别统计。

08接单状态:防并发抢单

输入律师点「接单」,输入是订单 id、律师 id 与当前 version。

状态先用条件更新占用「在线且空闲」的律师,再按 version 改订单状态,任一步影响行数为 0 就抛错回滚;两人同时点只有一人成功。

输出一条确定的接单结果与实时回显的在线状态,不会出现一单被两个律师接走。

09认证管理:资质审核闭环

输入律师提交执业证号与资质材料,管理员在 Vue 后台逐条查看。

状态认证在「待审核 → 已通过 / 已驳回」之间流转,只有管理员角色能改;未通过的律师不进入可接单列表。

输出一份带审核记录的认证结果,前台「已认证」标识与后台列表取同一个字段。

【在线律师咨询】实拍 03|咨询会话消息、律师主页资料、会员权益与续费;截图数据为演示数据

ARCHITECTURE / 三端分工

三端分工:哪些必须在服务端

端负责什么不该做什么
用户端找律师、下咨询单、看会员权益与消息不自己算剩余次数、不判断能否接单
管理后台律师建档、认证审核、分类维护、订单查询不并发改状态、不绕过服务端直接改认证
服务端订单状态机、权益扣减、抢单、权限与幂等不信任前端传的状态与金额,不裸奔开放接口

一句话原则:凡影响订单状态、权益次数、认证结果和「谁能看到谁的资料」的判断,都必须在服务端算,前端只负责把结果展示好。

CODE / 数据库与核心代码

数据库与核心代码

下面 7 段是能直接对照的代码:从表结构到接单幂等,覆盖这条链路上最容易出问题的几个点。

以下均为示例实现,生产环境需补充参数校验、权限、日志、监控、限流、支付验签等。

01表结构:四张表装下律师到订单

分类、律师、会员权益、咨询订单各司其职,订单表用 uk_request 唯一键挡住重复提交。

SQL

-- 在线律师咨询系统:核心表(MySQL 8.0)CREATE TABLE t_category (  id BIGINT PRIMARY KEY AUTO_INCREMENT,  parent_id BIGINT DEFAULT 0,  name VARCHAR(32) NOT NULL,  sort INT DEFAULT 0,  KEY idx_parent (parent_id));CREATE TABLE t_lawyer (  id BIGINT PRIMARY KEY AUTO_INCREMENT,  user_id BIGINT NOT NULL,  real_name VARCHAR(32) NOT NULL,  cert_no VARCHAR(64) DEFAULT '',  category_id BIGINT NOT NULL,  service_years INT DEFAULT 0,  online TINYINT DEFAULT 0,  accept_status TINYINT DEFAULT 0,  cert_status TINYINT DEFAULT 0,  version INT DEFAULT 0,  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,  UNIQUE KEY uk_user (user_id),  KEY idx_cat (category_id, online));CREATE TABLE t_member_benefit (  id BIGINT PRIMARY KEY AUTO_INCREMENT,  user_id BIGINT NOT NULL,  level TINYINT DEFAULT 0,  free_times INT DEFAULT 0,  expire_at DATETIME,  version INT DEFAULT 0,  UNIQUE KEY uk_user (user_id));CREATE TABLE t_consult_order (  id BIGINT PRIMARY KEY AUTO_INCREMENT,  request_no VARCHAR(40) NOT NULL,  user_id BIGINT NOT NULL,  lawyer_id BIGINT DEFAULT 0,  category_id BIGINT NOT NULL,  consult_type TINYINT DEFAULT 1,  content VARCHAR(1000) DEFAULT '',  amount INT DEFAULT 0,  status TINYINT DEFAULT 0,  version INT DEFAULT 0,  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,  UNIQUE KEY uk_request (request_no),  KEY idx_user (user_id, status),  KEY idx_lawyer (lawyer_id, status));

示例实现,生产环境需补充参数校验、权限、日志、监控、限流等。

02下单:落订单与扣次数同事务

订单落库与权益次数扣减必须同事务,否则会出现单建好了但次数没扣。

JAVA

// 下单:落订单 + 扣会员次数,同一事务@Transactional(rollbackFor = Exception.class)public OrderVO create(OrderDTO o, Long uid) {    // 条件扣减,次数不足时影响行数为 0    int n = benefitMapper.deduct(uid);    if (n == 0) {        throw new BizException("权益次数不足");    }    ConsultOrder d = new ConsultOrder();    d.setRequestNo(o.getRequestNo());    d.setUserId(uid);    d.setLawyerId(0L);    d.setCategoryId(o.getCategoryId());    d.setConsultType(o.getConsultType());    d.setContent(o.getContent());    d.setAmount(o.getAmount());    d.setStatus(0);    orderMapper.insert(d);    return OrderVO.of(d);}// 权益次数只按条件扣,不做先查后改public interface BenefitMapper {    @Update("UPDATE t_member_benefit " +            "SET free_times = free_times - 1 " +            "WHERE user_id = #{uid} " +            "AND free_times >= 1 " +            "AND expire_at > NOW()")    int deduct(@Param("uid") Long uid);}

示例实现,生产环境需补充参数校验、权限、日志、监控、限流等。

03接单:条件更新防并发抢单

两个律师同时点接单,靠 version 与影响行数判定,只有一个能成功。

JAVA

// 接单:条件更新 + version,避免并发抢单public void accept(Long orderId, Long lawyerId,                   Long uid, Integer version) {    // 先占律师:本人、在线、当前未接单    int l = lawyerMapper.occupy(lawyerId, uid);    if (l == 0) {        throw new BizException("律师状态已变化");    }    // 再改订单,只认影响行数    int n = orderMapper.accept(            orderId, lawyerId, version);    if (n == 0) {        throw new BizException("订单已被接走");    }}public interface OrderMapper {    @Update("UPDATE t_consult_order " +            "SET status=1, lawyer_id=#{lid}, " +            "version=version+1 " +            "WHERE id=#{id} AND status=0 " +            "AND version=#{v}")    int accept(@Param("id") Long id,               @Param("lid") Long lid,               @Param("v") Integer v);}// 律师接单状态:在线且空闲才占用@Update("UPDATE t_lawyer " +        "SET accept_status=1, " +        "version=version+1 " +        "WHERE id=#{lid} AND user_id=#{uid} " +        "AND online=1 AND accept_status=0 " +        "AND version=#{v}")int occupy(@Param("lid") Long lid,           @Param("uid") Long uid,           @Param("v") Integer v);

示例实现,生产环境需补充参数校验、权限、日志、监控、限流等。

04幂等:请求号 + Redis + 唯一键

请求号查重、Redis 抢占、唯一键兜底三层,重复提交只产生一单。

JAVA

// 下单幂等:请求号 + Redis + 唯一键public OrderVO once(OrderDTO o, Long uid) {    String rq = o.getRequestNo();    // 1. 请求号查重,命中直接返回    ConsultOrder old =            orderMapper.byRequestNo(rq);    if (old != null) {        return OrderVO.of(old);    }    // 2. Redis 抢占,防并发重复提交    boolean ok = redis.setIfAbsent(            "order:" + rq, "1", 30);    if (!ok) {        throw new BizException("请求处理中");    }    try {        return create(o, uid);    } catch (DuplicateKeyException e) {        // 3. 只放过 uk_request 冲突        String msg = "" + e.getMessage();        if (!msg.contains("uk_request")) {            throw e;        }        ConsultOrder d =                orderMapper.byRequestNo(rq);        return OrderVO.of(d);    }}

示例实现,生产环境需补充参数校验、权限、日志、监控、限流等。

05权限:律师只看自己的咨询

认证审核只有管理员可做,律师查订单时显式校验归属,不靠前端传参。

JAVA

// 权限:律师只看自己订单,认证需管理员@PreAuthorize("hasRole('LAWYER')")public OrderVO detail(Long orderId, Long uid) {    // 显式校验归属,不靠前端传参    ConsultOrder d = orderMapper.byId(orderId);    if (d == null) {        throw new BizException("订单不存在");    }    Lawyer lw = lawyerMapper.byUserId(uid);    if (lw == null ||            !lw.getId().equals(d.getLawyerId())) {        throw new BizException("无权查看该咨询");    }    return OrderVO.of(d);}// 认证管理:仅管理员可审核@PreAuthorize("hasRole('ADMIN')")public void certify(Long lawyerId, int status) {    if (status != 1 && status != 2) {        throw new BizException("状态不合法");    }    lawyerMapper.updateCert(lawyerId, status);}// 精准分类:只返回启用的一级/二级public List<CategoryVO> tree() {    return categoryMapper.tree(0L);}

示例实现,生产环境需补充参数校验、权限、日志、监控、限流等。

06多端请求:统一带 token 与请求号

小程序、公众号、H5 共用一套请求封装,401 统一跳登录。

JAVASCRIPT

// 多端统一请求:小程序 + 公众号 + H5const BASE = 'https://api.example.com';function platform() {  // #ifdef MP-WEIXIN  return 'mp';  // #endif  // #ifdef H5  return 'h5';  // #endif  return 'wx';}function genRequestNo() {  return Date.now() + '-' +    Math.random().toString(36).slice(2, 10);}export function request(path, data = {}) {  return new Promise((resolve, reject) => {    const rq = genRequestNo();    uni.request({      url: BASE + path,      method: 'POST',      data: { ...data, requestNo: rq },      header: {        'X-Token': uni.getStorageSync('token'),        'X-Request-No': rq,        'X-Platform': platform()      },      success(res) {        if (res.statusCode === 401) {          uni.navigateTo({ url: '/pages/login' });          reject(new Error('未登录'));          return;        }        if (res.data.code === 0) {          resolve(res.data.data);        } else {          reject(new Error(res.data.msg));        }      },      fail: reject    });  });}// 图文咨询 / 电话咨询共用一个下单口export function createConsult(o) {  return request('/consult/create', o);}

示例实现,生产环境需补充参数校验、权限、日志、监控、限流等。

07后台表格:认证审核与状态标签

后台要的是分页列表、状态标签和通过 / 驳回两个操作,不堆花哨交互。

VUE

<template>  <div class="cert-list">    <el-table :data="list" v-loading="loading">      <el-table-column prop="realName"        label="律师" />      <el-table-column prop="certNo"        label="执业证号" />      <el-table-column label="认证状态">        <template #default="{ row }">          <el-tag :type="tagType(row.certStatus)">            {{ statusText(row.certStatus) }}          </el-tag>        </template>      </el-table-column>      <el-table-column label="操作" width="180">        <template #default="{ row }">          <el-button size="mini"            @click="pass(row)">通过</el-button>          <el-button size="mini" type="danger"            @click="reject(row)">驳回</el-button>        </template>      </el-table-column>    </el-table>    <el-pagination      :current-page="page"      :page-size="20"      :total="total"      layout="prev, pager, next"      @current-change="load" />  </div></template><script>export default {  data() {    return {      list: [], page: 1, total: 0,      loading: false    };  },  created() { this.load(1); },  methods: {    tagType(s) {      return s === 1 ? 'success'        : s === 2 ? 'danger' : 'warning';    },    statusText(s) {      return s === 1 ? '已通过'        : s === 2 ? '已驳回' : '待审核';    },    async load(p) {      this.page = p;      this.loading = true;      const res = await api.certList({ page: p });      this.list = res.records;      this.total = res.total;      this.loading = false;    },    async audit(row, status) {      await api.certify(row.id, status);      this.load(this.page);    },    pass(row) { this.audit(row, 1); },    reject(row) { this.audit(row, 2); }  }};</script>

示例实现,生产环境需补充参数校验、权限、日志、监控、限流等。

COMPLIANCE / 合规与上线

合规与上线检查

执业资质:律师执业证号与认证材料需可核验,未通过认证的账号不得展示「已认证」标识,也不得以律师身份接单。

个人信息:用户描述、上传材料与联系方式按最小必要收集,存储加密,查看与导出留痕。

咨询内容:图文描述与通话记录属敏感内容,需权限隔离、留存期限与删除机制,日志不打印明文。

服务边界:平台提供信息撮合与记录,不替代法律意见、不对结果作承诺,页面需有明确提示。

上线前建议逐项打勾

☐ 律师认证状态与前台「已认证」标识同源☐ 下单与权益次数扣减在同一事务☐ 订单请求号建唯一键,重复提交只产生一单☐ 接单用条件更新 + version,防并发抢单☐ 律师只能查看自己名下的咨询订单☐ 咨询描述与附件按权限隔离,日志不打印明文☐ 后台审核、导出有角色控制与操作日志☐ 多端请求统一带 token、请求号与平台标记

FIT / 适合谁

适合谁

适合想做律师在线咨询平台、律所线上获客或法律服务撮合的团队:需要把律师、订单、会员与认证收进一套后台。

不太适合只需要一个律所介绍页、不做下单与接单的场景,那是官网,不是这套系统。

二次开发表结构、状态机与三端分工已定好,换品牌、换领域分类就能用,适合行业二开的开发者与小团队。

FAQ / 常见问题

五个被问得最多的问题

Q:为什么接单要用 version 而不是先查再改?A:先查再改在并发下两个律师都会读到「待接单」,会一单两接;条件更新加 version 只认影响行数,谁先改成功谁接走。

Q:图文咨询和电话咨询要分两张表吗?A:不用。两者结构一样,用 consult_type 区分即可,后台按类型分别统计,维护成本更低。

Q:三端(小程序 / 公众号 / H5)代码能共用吗?A:能。uni-app 一套代码编译到三端,请求层统一封装,只有 platform 标记与登录方式不同。

Q:会员免费次数怎么防止被刷?A:用条件更新 free_times >= 1 扣减,配合请求号唯一键与事务,重复提交不会多扣也不会多建单。

Q:能不能只买小程序端,不要管理后台?A:不能。律师建档、认证审核、分类维护都要后台,服务端接口也需要后台调用;小程序只是展示与下单端。

LAST / 写在最后

这套系统的价值不在功能多少,而在每一环都有记录可查:谁建的律师档案、谁审的认证、谁接的单、权益次数扣在哪一单上。

代码是示例实现,表结构和状态机可以按实际业务调整;发布前建议按上面的打勾清单过一遍权限与合规。

如果你正在做律师在线咨询平台,或想基于 SpringBoot + MyBatisPlus + uni-app 做一套可二开的系统,欢迎找我领取演示资料和部署说明。

文中截图均为演示数据,界面与文案随版本可能调整;具体功能以实际交付版本为准。

回首页,搜索你想要的文档

在首页搜索框输入关键词,快速定位资料

文库首页搜索指引 立即搜索

相关学习资料