From b5fdbf131917b3cf0e6a5d595a233a4ded8fc773 Mon Sep 17 00:00:00 2001 From: ShiQi <15883326+shirenan@user.noreply.gitee.com> Date: Mon, 5 Jan 2026 17:11:35 +0800 Subject: [PATCH] =?UTF-8?q?=E9=99=84=E8=BF=91=E5=9C=B0=E5=9D=80=E7=9A=84?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E7=BC=96=E5=86=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Zhibo/admin/src/api/gift.js | 4 +- Zhibo/admin/vue.config.js | 12 + .../controller/CommunityAdminController.java | 103 --- .../admin/controller/GiftAdminController.java | 598 +++--------------- .../src/main/resources/application.yml | 1 + .../java/com/zbkj/common/model/user/User.java | 9 + .../request/GiftRecordSearchRequest.java | 32 + .../com/zbkj/common/request/GiftRequest.java | 59 ++ .../common/request/GiftSearchRequest.java | 29 + .../zbkj/common/request/UserEditRequest.java | 8 +- .../common/token/FrontTokenComponent.java | 1 + .../com/zbkj/common/utils/UploadUtil.java | 55 +- .../zbkj/common/vo/NearbyUserSimpleVO.java | 35 + .../java/com/zbkj/front/config/WebConfig.java | 26 + .../controller/CommunityFrontController.java | 20 + .../zbkj/front/controller/UserController.java | 113 ++++ .../src/main/resources/logback-spring.xml | 18 +- .../service/service/CommunityService.java | 5 + .../service/service/GiftRecordService.java | 16 + .../com/zbkj/service/service/GiftService.java | 28 + .../service/impl/CommunityServiceImpl.java | 170 ++++- .../service/impl/GiftRecordServiceImpl.java | 117 +++- .../service/service/impl/GiftServiceImpl.java | 87 +++ .../service/impl/UploadServiceImpl.java | 71 ++- .../service/service/impl/UserServiceImpl.java | 95 ++- android-app/app/src/main/AndroidManifest.xml | 3 + .../livestreaming/EditProfileActivity.java | 357 +++++++++-- .../example/livestreaming/MainActivity.java | 270 ++++++-- .../livestreaming/ProfileActivity.java | 317 ++++++++++ .../livestreaming/SettingsPageActivity.java | 5 +- .../location/TianDiTuLocationService.java | 360 +++++++++++ .../example/livestreaming/net/ApiService.java | 108 ++++ .../livestreaming/net/CommunityResponse.java | 48 ++ .../livestreaming/net/UserEditRequest.java | 10 + .../main/res/layout/activity_edit_profile.xml | 30 +- .../src/main/res/layout/layout_nearby_tab.xml | 3 +- 36 files changed, 2428 insertions(+), 795 deletions(-) delete mode 100644 Zhibo/zhibo-h/crmeb-admin/src/main/java/com/zbkj/admin/controller/CommunityAdminController.java create mode 100644 Zhibo/zhibo-h/crmeb-common/src/main/java/com/zbkj/common/request/GiftRecordSearchRequest.java create mode 100644 Zhibo/zhibo-h/crmeb-common/src/main/java/com/zbkj/common/request/GiftRequest.java create mode 100644 Zhibo/zhibo-h/crmeb-common/src/main/java/com/zbkj/common/request/GiftSearchRequest.java create mode 100644 Zhibo/zhibo-h/crmeb-common/src/main/java/com/zbkj/common/vo/NearbyUserSimpleVO.java create mode 100644 android-app/app/src/main/java/com/example/livestreaming/location/TianDiTuLocationService.java diff --git a/Zhibo/admin/src/api/gift.js b/Zhibo/admin/src/api/gift.js index c3789dd2..9112eb4b 100644 --- a/Zhibo/admin/src/api/gift.js +++ b/Zhibo/admin/src/api/gift.js @@ -111,7 +111,7 @@ export function giftDeleteApi(ids) { return request({ url: '/admin/gift/delete', method: 'post', - data: { ids: Array.isArray(ids) ? ids : [ids] } + data: Array.isArray(ids) ? ids : [ids] }) } @@ -120,6 +120,6 @@ export function giftStatusApi(id, status) { return request({ url: '/admin/gift/status', method: 'post', - data: { id, status } + params: { id, status } }) } diff --git a/Zhibo/admin/vue.config.js b/Zhibo/admin/vue.config.js index 1a33e546..4efdafed 100644 --- a/Zhibo/admin/vue.config.js +++ b/Zhibo/admin/vue.config.js @@ -61,6 +61,18 @@ module.exports = { '/file': { target: 'http://localhost:30001', changeOrigin: true + }, + '/image': { + target: 'http://localhost:30001', + changeOrigin: true + }, + '/video': { + target: 'http://localhost:30001', + changeOrigin: true + }, + '/voice': { + target: 'http://localhost:30001', + changeOrigin: true } } }, diff --git a/Zhibo/zhibo-h/crmeb-admin/src/main/java/com/zbkj/admin/controller/CommunityAdminController.java b/Zhibo/zhibo-h/crmeb-admin/src/main/java/com/zbkj/admin/controller/CommunityAdminController.java deleted file mode 100644 index 0248f061..00000000 --- a/Zhibo/zhibo-h/crmeb-admin/src/main/java/com/zbkj/admin/controller/CommunityAdminController.java +++ /dev/null @@ -1,103 +0,0 @@ -package com.zbkj.admin.controller; - -import com.github.pagehelper.PageInfo; -import com.zbkj.common.model.community.CommunityCategory; -import com.zbkj.common.model.community.CommunityMatchConfig; -import com.zbkj.common.request.AuditRequest; -import com.zbkj.common.request.CommunityMessageRequest; -import com.zbkj.common.result.CommonResult; -import com.zbkj.common.vo.CommunityMessageVO; -import com.zbkj.service.service.CommunityService; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.*; - -import java.util.List; - -/** - * 缘池管理控制器 - */ -@Slf4j -@RestController -@RequestMapping("api/admin/community") -@Api(tags = "缘池管理") -public class CommunityAdminController { - - @Autowired - private CommunityService communityService; - - // ==================== 板块管理 ==================== - //@PreAuthorize("hasAuthority('admin:community:category')") - @ApiOperation("板块列表") - @GetMapping("/category/list") - public CommonResult> categoryList() { - return CommonResult.success(communityService.getCategoryList()); - } - - //@PreAuthorize("hasAuthority('admin:community:category')") - @ApiOperation("保存板块") - @PostMapping("/category/save") - public CommonResult categorySave(@RequestBody CommunityCategory category) { - communityService.saveCategory(category); - return CommonResult.success("保存成功"); - } - - //@PreAuthorize("hasAuthority('admin:community:category')") - @ApiOperation("删除板块") - @DeleteMapping("/category/delete/{id}") - public CommonResult categoryDelete(@PathVariable Integer id) { - communityService.deleteCategory(id); - return CommonResult.success("删除成功"); - } - - //@PreAuthorize("hasAuthority('admin:community:category')") - @ApiOperation("更新板块状态") - @PostMapping("/category/status") - public CommonResult categoryStatus(@RequestBody CommunityCategory category) { - communityService.updateCategoryStatus(category.getId(), category.getStatus()); - return CommonResult.success("更新成功"); - } - - // ==================== 消息管理 ==================== - //@PreAuthorize("hasAuthority('admin:community:message')") - @ApiOperation("消息列表") - @PostMapping("/message/list") - public CommonResult> messageList(@RequestBody CommunityMessageRequest request) { - return CommonResult.success(communityService.getMessageList(request)); - } - - //@PreAuthorize("hasAuthority('admin:community:message')") - @ApiOperation("审核消息") - @PostMapping("/message/audit") - public CommonResult messageAudit(@RequestBody AuditRequest request) { - communityService.auditMessage(request.getId(), request.getStatus(), request.getRemark()); - return CommonResult.success("审核成功"); - } - - //@PreAuthorize("hasAuthority('admin:community:message')") - @ApiOperation("删除消息") - @DeleteMapping("/message/delete/{id}") - public CommonResult messageDelete(@PathVariable Long id) { - communityService.deleteMessage(id); - return CommonResult.success("删除成功"); - } - - // ==================== 匹配配置 ==================== - //@PreAuthorize("hasAuthority('admin:community:match')") - @ApiOperation("获取匹配配置") - @GetMapping("/match/config") - public CommonResult getMatchConfig() { - return CommonResult.success(communityService.getMatchConfig()); - } - - //@PreAuthorize("hasAuthority('admin:community:match')") - @ApiOperation("保存匹配配置") - @PostMapping("/match/config/save") - public CommonResult saveMatchConfig(@RequestBody CommunityMatchConfig config) { - communityService.saveMatchConfig(config); - return CommonResult.success("保存成功"); - } -} diff --git a/Zhibo/zhibo-h/crmeb-admin/src/main/java/com/zbkj/admin/controller/GiftAdminController.java b/Zhibo/zhibo-h/crmeb-admin/src/main/java/com/zbkj/admin/controller/GiftAdminController.java index 6528b8d0..4832940c 100644 --- a/Zhibo/zhibo-h/crmeb-admin/src/main/java/com/zbkj/admin/controller/GiftAdminController.java +++ b/Zhibo/zhibo-h/crmeb-admin/src/main/java/com/zbkj/admin/controller/GiftAdminController.java @@ -1,20 +1,28 @@ package com.zbkj.admin.controller; +import com.zbkj.common.model.gift.Gift; import com.zbkj.common.page.CommonPage; +import com.zbkj.common.request.GiftRecordSearchRequest; +import com.zbkj.common.request.GiftRequest; +import com.zbkj.common.request.GiftSearchRequest; import com.zbkj.common.result.CommonResult; +import com.zbkj.service.service.GiftRecordService; +import com.zbkj.service.service.GiftService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import java.math.BigDecimal; -import java.util.*; +import javax.validation.Valid; +import java.util.List; +import java.util.Map; /** - * 礼物管理控制器(后台管理) + * 礼物管理控制器(后台管理)- MVC架构 + * Controller层:负责接收请求、参数校验、调用Service层、返回响应 */ @Slf4j @RestController @@ -24,386 +32,21 @@ import java.util.*; public class GiftAdminController { @Autowired - private JdbcTemplate jdbcTemplate; + private GiftService giftService; + + @Autowired + private GiftRecordService giftRecordService; /** - * 获取礼物打赏记录列表 - */ - @ApiOperation(value = "礼物打赏记录列表") - @GetMapping("/records") - public CommonResult>> getGiftRecords( - @RequestParam(value = "keyword", required = false) String keyword, - @RequestParam(value = "startDate", required = false) String startDate, - @RequestParam(value = "endDate", required = false) String endDate, - @RequestParam(value = "page", defaultValue = "1") Integer page, - @RequestParam(value = "limit", defaultValue = "20") Integer limit) { - - StringBuilder sql = new StringBuilder(); - sql.append("SELECT g.id, g.gift_name, g.gift_image as gift_icon, g.gift_price, g.quantity, g.total_price, "); - sql.append("g.sender_id, COALESCE(g.sender_nickname, sender.nickname, '') as sender_nickname, "); - sql.append("COALESCE(g.sender_avatar, sender.avatar, '') as sender_avatar, COALESCE(sender.phone, '') as sender_phone, "); - sql.append("g.receiver_id, COALESCE(g.receiver_nickname, receiver.nickname, '') as receiver_nickname, "); - sql.append("COALESCE(receiver.avatar, '') as receiver_avatar, COALESCE(receiver.phone, '') as receiver_phone, "); - sql.append("g.room_id, COALESCE(r.title, '') as room_title, "); - sql.append("COALESCE(g.is_deleted, 0) as is_anonymous, g.create_time "); - sql.append("FROM eb_gift_record g "); - sql.append("LEFT JOIN eb_user sender ON g.sender_id = sender.uid "); - sql.append("LEFT JOIN eb_user receiver ON g.receiver_id = receiver.uid "); - sql.append("LEFT JOIN eb_live_room r ON g.room_id = r.id "); - sql.append("WHERE 1=1 "); - - StringBuilder countSql = new StringBuilder(); - countSql.append("SELECT COUNT(*) FROM eb_gift_record g "); - countSql.append("LEFT JOIN eb_user sender ON g.sender_id = sender.uid "); - countSql.append("LEFT JOIN eb_user receiver ON g.receiver_id = receiver.uid "); - countSql.append("WHERE 1=1 "); - - List params = new ArrayList<>(); - List countParams = new ArrayList<>(); - - if (keyword != null && !keyword.isEmpty()) { - String condition = " AND (g.sender_nickname LIKE ? OR g.receiver_nickname LIKE ? OR g.gift_name LIKE ?) "; - sql.append(condition); - countSql.append(condition); - String keywordPattern = "%" + keyword + "%"; - params.add(keywordPattern); - params.add(keywordPattern); - params.add(keywordPattern); - countParams.add(keywordPattern); - countParams.add(keywordPattern); - countParams.add(keywordPattern); - } - - if (startDate != null && !startDate.isEmpty()) { - String condition = " AND g.create_time >= ? "; - sql.append(condition); - countSql.append(condition); - params.add(startDate + " 00:00:00"); - countParams.add(startDate + " 00:00:00"); - } - - if (endDate != null && !endDate.isEmpty()) { - String condition = " AND g.create_time <= ? "; - sql.append(condition); - countSql.append(condition); - params.add(endDate + " 23:59:59"); - countParams.add(endDate + " 23:59:59"); - } - - sql.append(" ORDER BY g.create_time DESC "); - - Long total = jdbcTemplate.queryForObject(countSql.toString(), Long.class, countParams.toArray()); - - int offset = (page - 1) * limit; - sql.append(" LIMIT ? OFFSET ? "); - params.add(limit); - params.add(offset); - - List> list = jdbcTemplate.queryForList(sql.toString(), params.toArray()); - - CommonPage> result = new CommonPage<>(); - result.setList(list); - result.setTotal(total != null ? total : 0L); - result.setPage(page); - result.setLimit(limit); - result.setTotalPage((int) Math.ceil((double) (total != null ? total : 0) / limit)); - - return CommonResult.success(result); - } - - /** - * 获取礼物统计数据 - */ - @ApiOperation(value = "礼物统计数据") - @GetMapping("/statistics") - public CommonResult> getGiftStatistics() { - try { - Map stats = new HashMap<>(); - - // 总礼物数量 - String totalCountSql = "SELECT COUNT(*) FROM eb_gift_record"; - Integer totalCount = jdbcTemplate.queryForObject(totalCountSql, Integer.class); - stats.put("totalCount", totalCount != null ? totalCount : 0); - - // 总礼物价值 - String totalValueSql = "SELECT COALESCE(SUM(total_price), 0) FROM eb_gift_record"; - BigDecimal totalValue = jdbcTemplate.queryForObject(totalValueSql, BigDecimal.class); - stats.put("totalValue", totalValue != null ? totalValue : BigDecimal.ZERO); - - // 今日礼物数量 - String todayCountSql = "SELECT COUNT(*) FROM eb_gift_record WHERE DATE(create_time) = CURDATE()"; - Integer todayCount = jdbcTemplate.queryForObject(todayCountSql, Integer.class); - stats.put("todayCount", todayCount != null ? todayCount : 0); - - // 今日礼物价值 - String todayValueSql = "SELECT COALESCE(SUM(total_price), 0) FROM eb_gift_record WHERE DATE(create_time) = CURDATE()"; - BigDecimal todayValue = jdbcTemplate.queryForObject(todayValueSql, BigDecimal.class); - stats.put("todayValue", todayValue != null ? todayValue : BigDecimal.ZERO); - - return CommonResult.success(stats); - } catch (Exception e) { - log.error("获取礼物统计失败", e); - return CommonResult.failed("获取统计失败"); - } - } - - /** - * 获取礼物配置列表 - */ - @ApiOperation(value = "礼物配置列表") - @GetMapping("/config/list") - public CommonResult>> getGiftConfigList() { - try { - String sql = "SELECT id, name, icon, price, animation, sort_order, is_enabled, create_time " + - "FROM eb_gift_config ORDER BY sort_order"; - List> list = jdbcTemplate.queryForList(sql); - return CommonResult.success(list); - } catch (Exception e) { - log.error("获取礼物配置失败", e); - return CommonResult.failed("获取礼物配置失败"); - } - } - - /** - * 添加礼物配置 - */ - @ApiOperation(value = "添加礼物配置") - @PostMapping("/config/add") - public CommonResult addGiftConfig(@RequestBody Map request) { - try { - String name = (String) request.get("name"); - String icon = (String) request.get("icon"); - BigDecimal price = new BigDecimal(request.get("price").toString()); - String animation = (String) request.get("animation"); - Integer sortOrder = request.get("sortOrder") != null ? (Integer) request.get("sortOrder") : 0; - - String sql = "INSERT INTO eb_gift_config (name, icon, price, animation, sort_order) VALUES (?, ?, ?, ?, ?)"; - jdbcTemplate.update(sql, name, icon, price, animation, sortOrder); - - return CommonResult.success("添加成功"); - } catch (Exception e) { - log.error("添加礼物配置失败", e); - return CommonResult.failed("添加失败"); - } - } - - /** - * 更新礼物配置 - */ - @ApiOperation(value = "更新礼物配置") - @PostMapping("/config/update") - public CommonResult updateGiftConfig(@RequestBody Map request) { - try { - Integer id = (Integer) request.get("id"); - String name = (String) request.get("name"); - String icon = (String) request.get("icon"); - BigDecimal price = new BigDecimal(request.get("price").toString()); - String animation = (String) request.get("animation"); - Integer sortOrder = request.get("sortOrder") != null ? (Integer) request.get("sortOrder") : 0; - Integer isEnabled = request.get("isEnabled") != null ? (Integer) request.get("isEnabled") : 1; - - String sql = "UPDATE eb_gift_config SET name = ?, icon = ?, price = ?, animation = ?, sort_order = ?, is_enabled = ? WHERE id = ?"; - jdbcTemplate.update(sql, name, icon, price, animation, sortOrder, isEnabled, id); - - return CommonResult.success("更新成功"); - } catch (Exception e) { - log.error("更新礼物配置失败", e); - return CommonResult.failed("更新失败"); - } - } - - /** - * 删除礼物配置 - */ - @ApiOperation(value = "删除礼物配置") - @PostMapping("/config/delete/{id}") - public CommonResult deleteGiftConfig(@PathVariable Integer id) { - try { - String sql = "DELETE FROM eb_gift_config WHERE id = ?"; - jdbcTemplate.update(sql, id); - return CommonResult.success("删除成功"); - } catch (Exception e) { - log.error("删除礼物配置失败", e); - return CommonResult.failed("删除失败"); - } - } - - /** - * 获取充值套餐列表 - */ - @ApiOperation(value = "充值套餐列表") - @GetMapping("/recharge/packages") - public CommonResult>> getRechargePackages() { - try { - String sql = "SELECT id, amount, virtual_amount, bonus_amount, title, description, is_hot, sort_order, is_enabled " + - "FROM eb_recharge_package ORDER BY sort_order"; - List> list = jdbcTemplate.queryForList(sql); - return CommonResult.success(list); - } catch (Exception e) { - log.error("获取充值套餐失败", e); - return CommonResult.failed("获取充值套餐失败"); - } - } - - /** - * 更新充值套餐 - */ - @ApiOperation(value = "更新充值套餐") - @PostMapping("/recharge/package/update") - public CommonResult updateRechargePackage(@RequestBody Map request) { - try { - Integer id = (Integer) request.get("id"); - BigDecimal amount = new BigDecimal(request.get("amount").toString()); - BigDecimal virtualAmount = new BigDecimal(request.get("virtualAmount").toString()); - BigDecimal bonusAmount = new BigDecimal(request.get("bonusAmount").toString()); - String title = (String) request.get("title"); - String description = (String) request.get("description"); - Integer isHot = request.get("isHot") != null ? (Integer) request.get("isHot") : 0; - Integer sortOrder = request.get("sortOrder") != null ? (Integer) request.get("sortOrder") : 0; - Integer isEnabled = request.get("isEnabled") != null ? (Integer) request.get("isEnabled") : 1; - - String sql = "UPDATE eb_recharge_package SET amount = ?, virtual_amount = ?, bonus_amount = ?, " + - "title = ?, description = ?, is_hot = ?, sort_order = ?, is_enabled = ? WHERE id = ?"; - jdbcTemplate.update(sql, amount, virtualAmount, bonusAmount, title, description, isHot, sortOrder, isEnabled, id); - - return CommonResult.success("更新成功"); - } catch (Exception e) { - log.error("更新充值套餐失败", e); - return CommonResult.failed("更新失败"); - } - } - - /** - * 获取充值记录 - */ - @ApiOperation(value = "充值记录列表") - @GetMapping("/recharge/records") - public CommonResult>> getRechargeRecords( - @RequestParam(value = "keyword", required = false) String keyword, - @RequestParam(value = "page", defaultValue = "1") Integer page, - @RequestParam(value = "limit", defaultValue = "20") Integer limit) { - - StringBuilder sql = new StringBuilder(); - sql.append("SELECT r.id, r.order_no, r.amount, r.virtual_amount, r.payment_method, r.payment_status, "); - sql.append("r.create_time, r.pay_time, "); - sql.append("u.uid as user_id, u.nickname, u.phone, u.avatar "); - sql.append("FROM eb_virtual_currency_recharge r "); - sql.append("LEFT JOIN eb_user u ON r.user_id = u.uid "); - sql.append("WHERE 1=1 "); - - StringBuilder countSql = new StringBuilder(); - countSql.append("SELECT COUNT(*) FROM eb_virtual_currency_recharge r "); - countSql.append("LEFT JOIN eb_user u ON r.user_id = u.uid "); - countSql.append("WHERE 1=1 "); - - List params = new ArrayList<>(); - List countParams = new ArrayList<>(); - - if (keyword != null && !keyword.isEmpty()) { - String condition = " AND (u.nickname LIKE ? OR u.phone LIKE ? OR r.order_no LIKE ?) "; - sql.append(condition); - countSql.append(condition); - String keywordPattern = "%" + keyword + "%"; - params.add(keywordPattern); - params.add(keywordPattern); - params.add(keywordPattern); - countParams.add(keywordPattern); - countParams.add(keywordPattern); - countParams.add(keywordPattern); - } - - sql.append(" ORDER BY r.create_time DESC "); - - Long total = jdbcTemplate.queryForObject(countSql.toString(), Long.class, countParams.toArray()); - - int offset = (page - 1) * limit; - sql.append(" LIMIT ? OFFSET ? "); - params.add(limit); - params.add(offset); - - List> list = jdbcTemplate.queryForList(sql.toString(), params.toArray()); - - CommonPage> result = new CommonPage<>(); - result.setList(list); - result.setTotal(total != null ? total : 0L); - result.setPage(page); - result.setLimit(limit); - result.setTotalPage((int) Math.ceil((double) (total != null ? total : 0) / limit)); - - return CommonResult.success(result); - } - - // ========== 礼物管理接口(eb_gift表) ========== - - /** - * 获取礼物列表 + * 获取礼物列表(分页) */ @ApiOperation(value = "礼物列表") @GetMapping("/list") - public CommonResult>> getGiftList( - @RequestParam(value = "name", required = false) String name, - @RequestParam(value = "status", required = false) Integer status, - @RequestParam(value = "page", defaultValue = "1") Integer page, - @RequestParam(value = "limit", defaultValue = "20") Integer limit) { - - log.info("礼物列表查询 - 参数: name={}, status={}, page={}, limit={}", name, status, page, limit); - - StringBuilder sql = new StringBuilder(); - sql.append("SELECT id, name, image, diamond_price as diamondPrice, intimacy, status, "); - sql.append("is_heartbeat as isHeartbeat, buy_type as buyType, belong, remark, "); - sql.append("level, sort, create_time as createTime, update_time as updateTime "); - sql.append("FROM eb_gift WHERE is_deleted = 0 "); - - StringBuilder countSql = new StringBuilder(); - countSql.append("SELECT COUNT(*) FROM eb_gift WHERE is_deleted = 0 "); - - List params = new ArrayList<>(); - List countParams = new ArrayList<>(); - - if (name != null && !name.isEmpty()) { - String condition = " AND name LIKE ? "; - sql.append(condition); - countSql.append(condition); - String namePattern = "%" + name + "%"; - params.add(namePattern); - countParams.add(namePattern); - } - - if (status != null) { - String condition = " AND status = ? "; - sql.append(condition); - countSql.append(condition); - params.add(status); - countParams.add(status); - } - - sql.append(" ORDER BY sort ASC, id ASC "); - - Long total = jdbcTemplate.queryForObject(countSql.toString(), Long.class, countParams.toArray()); - if (total == null) { - total = 0L; - } - - int offset = (page - 1) * limit; - sql.append(" LIMIT ? OFFSET ? "); - params.add(limit); - params.add(offset); - - log.info("礼物列表查询 - SQL: {}", sql.toString()); - log.info("礼物列表查询 - 参数: {}", params); - - List> list = jdbcTemplate.queryForList(sql.toString(), params.toArray()); - - // 打印日志用于调试 - log.info("礼物列表查询 - 总数: {}, 当前页: {}, 每页: {}, offset: {}, 列表大小: {}", total, page, limit, offset, list.size()); - - CommonPage> result = new CommonPage<>(); - result.setList(list); - result.setTotal(total); - result.setPage(page); - result.setLimit(limit); - result.setTotalPage((int) Math.ceil((double) total / limit)); - + public CommonResult> getGiftList(GiftSearchRequest request) { + log.info("礼物列表查询 - 参数: {}", request); + CommonPage result = giftService.getGiftList(request); + log.info("礼物列表查询 - 总数: {}, 当前页: {}, 列表大小: {}", + result.getTotal(), result.getPage(), result.getList().size()); return CommonResult.success(result); } @@ -412,35 +55,19 @@ public class GiftAdminController { */ @ApiOperation(value = "添加礼物") @PostMapping("/add") - public CommonResult addGift(@RequestBody Map request) { - try { - log.info("添加礼物 - 请求参数: {}", request); - - String name = (String) request.get("name"); - String image = (String) request.get("image"); - BigDecimal diamondPrice = new BigDecimal(request.get("diamondPrice").toString()); - Integer intimacy = request.get("intimacy") != null ? Integer.parseInt(request.get("intimacy").toString()) : 0; - Integer level = request.get("level") != null ? Integer.parseInt(request.get("level").toString()) : 1; - - // 处理布尔值或整数值 - Integer isHeartbeat = convertToInteger(request.get("isHeartbeat"), 0); - Integer sort = request.get("sort") != null ? Integer.parseInt(request.get("sort").toString()) : 0; - Integer status = convertToInteger(request.get("status"), 1); - - String remark = (String) request.get("remark"); - String buyType = request.get("buyType") != null ? (String) request.get("buyType") : "钻石"; - String belong = request.get("belong") != null ? (String) request.get("belong") : "平台"; - - String sql = "INSERT INTO eb_gift (name, image, diamond_price, intimacy, status, is_heartbeat, " + - "buy_type, belong, remark, level, sort) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; - jdbcTemplate.update(sql, name, image, diamondPrice, intimacy, status, isHeartbeat, - buyType, belong, remark, level, sort); - - log.info("添加礼物成功 - 名称: {}", name); + public CommonResult addGift(@Valid @RequestBody GiftRequest request) { + log.info("添加礼物 - 请求参数: {}", request); + + Gift gift = new Gift(); + BeanUtils.copyProperties(request, gift); + + boolean success = giftService.save(gift); + if (success) { + log.info("添加礼物成功 - 名称: {}", request.getName()); return CommonResult.success("添加成功"); - } catch (Exception e) { - log.error("添加礼物失败", e); - return CommonResult.failed("添加失败: " + e.getMessage()); + } else { + log.error("添加礼物失败 - 名称: {}", request.getName()); + return CommonResult.failed("添加失败"); } } @@ -449,89 +76,49 @@ public class GiftAdminController { */ @ApiOperation(value = "更新礼物") @PostMapping("/update") - public CommonResult updateGift(@RequestBody Map request) { - try { - log.info("更新礼物 - 请求参数: {}", request); - - Integer id = Integer.parseInt(request.get("id").toString()); - String name = (String) request.get("name"); - String image = (String) request.get("image"); - BigDecimal diamondPrice = new BigDecimal(request.get("diamondPrice").toString()); - Integer intimacy = request.get("intimacy") != null ? Integer.parseInt(request.get("intimacy").toString()) : 0; - Integer level = request.get("level") != null ? Integer.parseInt(request.get("level").toString()) : 1; - - // 处理布尔值或整数值 - Integer isHeartbeat = convertToInteger(request.get("isHeartbeat"), 0); - Integer sort = request.get("sort") != null ? Integer.parseInt(request.get("sort").toString()) : 0; - Integer status = convertToInteger(request.get("status"), 1); - - String remark = (String) request.get("remark"); - String buyType = request.get("buyType") != null ? (String) request.get("buyType") : "钻石"; - String belong = request.get("belong") != null ? (String) request.get("belong") : "平台"; - - String sql = "UPDATE eb_gift SET name = ?, image = ?, diamond_price = ?, intimacy = ?, status = ?, " + - "is_heartbeat = ?, buy_type = ?, belong = ?, remark = ?, level = ?, sort = ? WHERE id = ?"; - jdbcTemplate.update(sql, name, image, diamondPrice, intimacy, status, isHeartbeat, - buyType, belong, remark, level, sort, id); - - log.info("更新礼物成功 - ID: {}", id); + public CommonResult updateGift(@Valid @RequestBody GiftRequest request) { + log.info("更新礼物 - 请求参数: {}", request); + + if (request.getId() == null) { + return CommonResult.failed("礼物ID不能为空"); + } + + Gift gift = giftService.getById(request.getId()); + if (gift == null) { + return CommonResult.failed("礼物不存在"); + } + + BeanUtils.copyProperties(request, gift); + + boolean success = giftService.updateById(gift); + if (success) { + log.info("更新礼物成功 - ID: {}", request.getId()); return CommonResult.success("更新成功"); - } catch (Exception e) { - log.error("更新礼物失败", e); - return CommonResult.failed("更新失败: " + e.getMessage()); - } - } - - /** - * 将对象转换为整数(支持布尔值、字符串、整数) - */ - private Integer convertToInteger(Object value, Integer defaultValue) { - if (value == null) { - return defaultValue; - } - - // 如果是布尔值 - if (value instanceof Boolean) { - return ((Boolean) value) ? 1 : 0; - } - - // 如果是字符串 - String strValue = value.toString().toLowerCase(); - if ("true".equals(strValue)) { - return 1; - } else if ("false".equals(strValue)) { - return 0; - } - - // 尝试解析为整数 - try { - return Integer.parseInt(strValue); - } catch (NumberFormatException e) { - return defaultValue; + } else { + log.error("更新礼物失败 - ID: {}", request.getId()); + return CommonResult.failed("更新失败"); } } /** - * 删除礼物 + * 删除礼物(支持批量删除) */ @ApiOperation(value = "删除礼物") @PostMapping("/delete") - public CommonResult deleteGift(@RequestBody Map request) { - try { - @SuppressWarnings("unchecked") - List ids = (List) request.get("ids"); - if (ids == null || ids.isEmpty()) { - return CommonResult.failed("请选择要删除的礼物"); - } - - String placeholders = String.join(",", Collections.nCopies(ids.size(), "?")); - String sql = "UPDATE eb_gift SET is_deleted = 1 WHERE id IN (" + placeholders + ")"; - jdbcTemplate.update(sql, ids.toArray()); - + public CommonResult deleteGift(@RequestBody List ids) { + log.info("删除礼物 - IDs: {}", ids); + + if (ids == null || ids.isEmpty()) { + return CommonResult.failed("请选择要删除的礼物"); + } + + boolean success = giftService.deleteGifts(ids); + if (success) { + log.info("删除礼物成功 - 数量: {}", ids.size()); return CommonResult.success("删除成功"); - } catch (Exception e) { - log.error("删除礼物失败", e); - return CommonResult.failed("删除失败: " + e.getMessage()); + } else { + log.error("删除礼物失败"); + return CommonResult.failed("删除失败"); } } @@ -540,21 +127,40 @@ public class GiftAdminController { */ @ApiOperation(value = "更新礼物状态") @PostMapping("/status") - public CommonResult updateGiftStatus(@RequestBody Map request) { - try { - log.info("更新礼物状态 - 请求参数: {}", request); - - Integer id = Integer.parseInt(request.get("id").toString()); - Integer status = convertToInteger(request.get("status"), 1); - - String sql = "UPDATE eb_gift SET status = ? WHERE id = ?"; - jdbcTemplate.update(sql, status, id); - - log.info("更新礼物状态成功 - ID: {}, 状态: {}", id, status); - return CommonResult.success("状态更新成功"); - } catch (Exception e) { - log.error("更新礼物状态失败", e); - return CommonResult.failed("状态更新失败: " + e.getMessage()); + public CommonResult updateStatus(@RequestParam Integer id, @RequestParam Integer status) { + log.info("更新礼物状态 - ID: {}, 状态: {}", id, status); + + boolean success = giftService.updateStatus(id, status); + if (success) { + log.info("更新礼物状态成功 - ID: {}", id); + return CommonResult.success("更新成功"); + } else { + log.error("更新礼物状态失败 - ID: {}", id); + return CommonResult.failed("更新失败"); } } + + /** + * 获取礼物统计数据(礼物记录统计) + */ + @ApiOperation(value = "礼物统计数据") + @GetMapping("/statistics") + public CommonResult> getGiftStatistics() { + log.info("获取礼物记录统计数据"); + Map statistics = giftRecordService.getGiftRecordStatistics(); + return CommonResult.success(statistics); + } + + /** + * 获取礼物打赏记录列表 + */ + @ApiOperation(value = "礼物打赏记录列表") + @GetMapping("/records") + public CommonResult>> getGiftRecords(GiftRecordSearchRequest request) { + log.info("获取礼物打赏记录 - 参数: {}", request); + CommonPage> result = giftRecordService.getGiftRecordList(request); + log.info("获取礼物打赏记录 - 总数: {}, 当前页: {}, 列表大小: {}", + result.getTotal(), result.getPage(), result.getList().size()); + return CommonResult.success(result); + } } diff --git a/Zhibo/zhibo-h/crmeb-admin/src/main/resources/application.yml b/Zhibo/zhibo-h/crmeb-admin/src/main/resources/application.yml index 5b3c9845..f4940980 100644 --- a/Zhibo/zhibo-h/crmeb-admin/src/main/resources/application.yml +++ b/Zhibo/zhibo-h/crmeb-admin/src/main/resources/application.yml @@ -136,6 +136,7 @@ mybatis-plus: # 配置slq打印日志 configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + map-underscore-to-camel-case: true # 开启驼峰命名转换:数据库字段 diamond_price -> Java属性 diamondPrice global-config: db-config: # logic-delete-field: isDel #全局逻辑删除字段值 3.3.0开始支持,详情看下面。 diff --git a/Zhibo/zhibo-h/crmeb-common/src/main/java/com/zbkj/common/model/user/User.java b/Zhibo/zhibo-h/crmeb-common/src/main/java/com/zbkj/common/model/user/User.java index 870eb816..037abe72 100644 --- a/Zhibo/zhibo-h/crmeb-common/src/main/java/com/zbkj/common/model/user/User.java +++ b/Zhibo/zhibo-h/crmeb-common/src/main/java/com/zbkj/common/model/user/User.java @@ -133,6 +133,15 @@ public class User implements Serializable { @ApiModelProperty(value = "详细地址") private String addres; + @ApiModelProperty(value = "纬度") + private BigDecimal latitude; + + @ApiModelProperty(value = "经度") + private BigDecimal longitude; + + @ApiModelProperty(value = "位置更新时间") + private Date locationUpdateTime; + @ApiModelProperty(value = "管理员编号 ") private Integer adminid; diff --git a/Zhibo/zhibo-h/crmeb-common/src/main/java/com/zbkj/common/request/GiftRecordSearchRequest.java b/Zhibo/zhibo-h/crmeb-common/src/main/java/com/zbkj/common/request/GiftRecordSearchRequest.java new file mode 100644 index 00000000..44aca193 --- /dev/null +++ b/Zhibo/zhibo-h/crmeb-common/src/main/java/com/zbkj/common/request/GiftRecordSearchRequest.java @@ -0,0 +1,32 @@ +package com.zbkj.common.request; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; + +/** + * 礼物记录搜索请求对象 + */ +@Data +@ApiModel(value = "GiftRecordSearchRequest", description = "礼物记录搜索请求") +public class GiftRecordSearchRequest implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "关键字(送礼人/收礼人/礼物名称)") + private String keyword; + + @ApiModelProperty(value = "开始日期") + private String startDate; + + @ApiModelProperty(value = "结束日期") + private String endDate; + + @ApiModelProperty(value = "页码", example = "1") + private Integer page = 1; + + @ApiModelProperty(value = "每页数量", example = "20") + private Integer limit = 20; +} diff --git a/Zhibo/zhibo-h/crmeb-common/src/main/java/com/zbkj/common/request/GiftRequest.java b/Zhibo/zhibo-h/crmeb-common/src/main/java/com/zbkj/common/request/GiftRequest.java new file mode 100644 index 00000000..24a62d09 --- /dev/null +++ b/Zhibo/zhibo-h/crmeb-common/src/main/java/com/zbkj/common/request/GiftRequest.java @@ -0,0 +1,59 @@ +package com.zbkj.common.request; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import java.io.Serializable; +import java.math.BigDecimal; + +/** + * 礼物请求对象 + */ +@Data +@ApiModel(value = "GiftRequest", description = "礼物请求") +public class GiftRequest implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "礼物ID(更新时必填)") + private Integer id; + + @ApiModelProperty(value = "礼物名称", required = true) + @NotBlank(message = "礼物名称不能为空") + private String name; + + @ApiModelProperty(value = "礼物图片地址", required = true) + @NotBlank(message = "礼物图片不能为空") + private String image; + + @ApiModelProperty(value = "钻石单价", required = true) + @NotNull(message = "钻石单价不能为空") + private BigDecimal diamondPrice; + + @ApiModelProperty(value = "增加亲密度值") + private Integer intimacy = 0; + + @ApiModelProperty(value = "礼物状态 1启用 0禁用") + private Integer status = 1; + + @ApiModelProperty(value = "是否心动礼物 1是 0否") + private Integer isHeartbeat = 0; + + @ApiModelProperty(value = "购买方式") + private String buyType = "钻石"; + + @ApiModelProperty(value = "礼物归属") + private String belong = "平台"; + + @ApiModelProperty(value = "备注") + private String remark; + + @ApiModelProperty(value = "礼物等级(1-5)") + private Integer level = 1; + + @ApiModelProperty(value = "排序") + private Integer sort = 0; +} diff --git a/Zhibo/zhibo-h/crmeb-common/src/main/java/com/zbkj/common/request/GiftSearchRequest.java b/Zhibo/zhibo-h/crmeb-common/src/main/java/com/zbkj/common/request/GiftSearchRequest.java new file mode 100644 index 00000000..1304d491 --- /dev/null +++ b/Zhibo/zhibo-h/crmeb-common/src/main/java/com/zbkj/common/request/GiftSearchRequest.java @@ -0,0 +1,29 @@ +package com.zbkj.common.request; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; + +/** + * 礼物搜索请求对象 + */ +@Data +@ApiModel(value = "GiftSearchRequest", description = "礼物搜索请求") +public class GiftSearchRequest implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "礼物名称") + private String name; + + @ApiModelProperty(value = "状态:1=启用 0=禁用") + private Integer status; + + @ApiModelProperty(value = "页码", example = "1") + private Integer page = 1; + + @ApiModelProperty(value = "每页数量", example = "10") + private Integer limit = 10; +} diff --git a/Zhibo/zhibo-h/crmeb-common/src/main/java/com/zbkj/common/request/UserEditRequest.java b/Zhibo/zhibo-h/crmeb-common/src/main/java/com/zbkj/common/request/UserEditRequest.java index d00d342d..3c289ace 100644 --- a/Zhibo/zhibo-h/crmeb-common/src/main/java/com/zbkj/common/request/UserEditRequest.java +++ b/Zhibo/zhibo-h/crmeb-common/src/main/java/com/zbkj/common/request/UserEditRequest.java @@ -9,6 +9,7 @@ import org.hibernate.validator.constraints.Length; import javax.validation.constraints.NotBlank; import java.io.Serializable; +import java.math.BigDecimal; /** * 用户编辑Request @@ -31,7 +32,6 @@ public class UserEditRequest implements Serializable { private static final long serialVersionUID=1L; @ApiModelProperty(value = "用户昵称") - @NotBlank(message = "请填写用户昵称") @Length(max = 255, message = "用户昵称不能超过255个字符") private String nickname; @@ -50,6 +50,12 @@ public class UserEditRequest implements Serializable { @Length(max = 255, message = "地址不能超过255个字符") private String addres; + @ApiModelProperty(value = "纬度") + private BigDecimal latitude; + + @ApiModelProperty(value = "经度") + private BigDecimal longitude; + @ApiModelProperty(value = "个人签名/备注") @Length(max = 255, message = "个人签名不能超过255个字符") private String mark; diff --git a/Zhibo/zhibo-h/crmeb-common/src/main/java/com/zbkj/common/token/FrontTokenComponent.java b/Zhibo/zhibo-h/crmeb-common/src/main/java/com/zbkj/common/token/FrontTokenComponent.java index 07d009aa..310c5126 100644 --- a/Zhibo/zhibo-h/crmeb-common/src/main/java/com/zbkj/common/token/FrontTokenComponent.java +++ b/Zhibo/zhibo-h/crmeb-common/src/main/java/com/zbkj/common/token/FrontTokenComponent.java @@ -208,6 +208,7 @@ public class FrontTokenComponent { "api/front/community/categories", "api/front/community/messages", "api/front/community/nearby-users", + "api/front/community/nearby-users-simple", "api/front/community/user-count", // 直播间公开接口 "api/front/live/public" diff --git a/Zhibo/zhibo-h/crmeb-common/src/main/java/com/zbkj/common/utils/UploadUtil.java b/Zhibo/zhibo-h/crmeb-common/src/main/java/com/zbkj/common/utils/UploadUtil.java index c10f611e..be0b5292 100644 --- a/Zhibo/zhibo-h/crmeb-common/src/main/java/com/zbkj/common/utils/UploadUtil.java +++ b/Zhibo/zhibo-h/crmeb-common/src/main/java/com/zbkj/common/utils/UploadUtil.java @@ -83,28 +83,59 @@ public class UploadUtil { /** * 根据文件的绝对路径创建一个文件对象. + * 注意:只创建目录,不创建文件本身(文件由transferTo创建) * @return 返回创建的这个文件对象 * @author Mr.Zhang * @since 2020-05-08 */ public static File createFile(String filePath) throws IOException { + System.out.println("=== 创建文件 ==="); + System.out.println("目标路径: " + filePath); + // 获取文件的完整目录 String fileDir = FilenameUtils.getFullPath(filePath); + System.out.println("目录路径: " + fileDir); + // 判断目录是否存在,不存在就创建一个目录 - File file = new File(fileDir); - if (!file.isDirectory()) { - //创建失败返回null - if (!file.mkdirs()) { - throw new CrmebException("文件目录创建失败..."); - } - } - // 判断这个文件是否存在,不存在就创建 - file = new File(filePath); - if (!file.exists()) { - if (!file.createNewFile()) { - throw new CrmebException("目标文件创建失败..."); + File dir = new File(fileDir); + System.out.println("目录是否存在: " + dir.exists()); + System.out.println("是否是目录: " + dir.isDirectory()); + + if (!dir.exists()) { + System.out.println("目录不存在,开始创建..."); + boolean created = dir.mkdirs(); + System.out.println("目录创建结果: " + created); + + if (!created) { + // 检查父目录权限 + File parent = dir.getParentFile(); + if (parent != null) { + System.out.println("父目录: " + parent.getAbsolutePath()); + System.out.println("父目录存在: " + parent.exists()); + System.out.println("父目录可写: " + parent.canWrite()); + System.out.println("父目录可读: " + parent.canRead()); + } + throw new CrmebException("文件目录创建失败: " + fileDir + + "\n请检查:1. 目录权限 2. 磁盘空间 3. 路径是否合法"); + } + System.out.println("目录创建成功"); + } else { + System.out.println("目录已存在"); + // 检查目录权限 + System.out.println("目录可写: " + dir.canWrite()); + System.out.println("目录可读: " + dir.canRead()); + + if (!dir.canWrite()) { + throw new CrmebException("目录没有写入权限: " + fileDir); } } + + // 创建文件对象(但不创建实际文件,让transferTo来创建) + File file = new File(filePath); + System.out.println("文件对象创建完成(未创建实际文件): " + file.getAbsolutePath()); + System.out.println("文件可写: " + file.getParentFile().canWrite()); + System.out.println("==============="); + return file; } diff --git a/Zhibo/zhibo-h/crmeb-common/src/main/java/com/zbkj/common/vo/NearbyUserSimpleVO.java b/Zhibo/zhibo-h/crmeb-common/src/main/java/com/zbkj/common/vo/NearbyUserSimpleVO.java new file mode 100644 index 00000000..647afccd --- /dev/null +++ b/Zhibo/zhibo-h/crmeb-common/src/main/java/com/zbkj/common/vo/NearbyUserSimpleVO.java @@ -0,0 +1,35 @@ +package com.zbkj.common.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; + +/** + * 附近用户简化VO - 只包含基本信息 + */ +@Data +@ApiModel(value = "NearbyUserSimpleVO", description = "附近用户简化VO") +public class NearbyUserSimpleVO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "用户ID") + private Integer id; + + @ApiModelProperty(value = "用户昵称") + private String nickname; + + @ApiModelProperty(value = "用户头像") + private String avatar; + + @ApiModelProperty(value = "用户地址") + private String address; + + @ApiModelProperty(value = "距离(公里)") + private Double distance; + + @ApiModelProperty(value = "距离文本(例如:1.2km)") + private String distanceText; +} diff --git a/Zhibo/zhibo-h/crmeb-front/src/main/java/com/zbkj/front/config/WebConfig.java b/Zhibo/zhibo-h/crmeb-front/src/main/java/com/zbkj/front/config/WebConfig.java index 35cb32a0..0af32ad5 100644 --- a/Zhibo/zhibo-h/crmeb-front/src/main/java/com/zbkj/front/config/WebConfig.java +++ b/Zhibo/zhibo-h/crmeb-front/src/main/java/com/zbkj/front/config/WebConfig.java @@ -1,8 +1,10 @@ package com.zbkj.front.config; +import com.zbkj.common.config.CrmebConfig; import com.zbkj.common.interceptor.SwaggerInterceptor; import com.zbkj.front.filter.ResponseFilter; import com.zbkj.front.interceptor.FrontTokenInterceptor; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.web.servlet.FilterRegistrationBean; @@ -108,6 +110,9 @@ public class WebConfig implements WebMvcConfigurer { excludePathPatterns("/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**"); } + @Autowired + CrmebConfig crmebConfig; + @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/**") @@ -116,6 +121,27 @@ public class WebConfig implements WebMvcConfigurer { .addResourceLocations("classpath:/META-INF/resources/"); registry.addResourceHandler("/webjars/**") .addResourceLocations("classpath:/META-INF/resources/webjars/"); + + // 使用统一的图片路径配置 - 与admin模块保持一致 + String uploadPath = crmebConfig.getAbsoluteImagePath(); + + // 添加 image 路径映射(修复图片上传显示问题) + registry.addResourceHandler("/image/**") + .addResourceLocations("file:" + uploadPath + "image" + java.io.File.separator); + + // 添加 video 路径映射 + registry.addResourceHandler("/video/**") + .addResourceLocations("file:" + uploadPath + "video" + java.io.File.separator); + + // 添加 voice 路径映射 + registry.addResourceHandler("/voice/**") + .addResourceLocations("file:" + uploadPath + "voice" + java.io.File.separator); + + registry.addResourceHandler("/crmebimage/**") + .addResourceLocations("file:" + uploadPath + "crmebimage" + java.io.File.separator); + + registry.addResourceHandler("/file/**") + .addResourceLocations("file:" + uploadPath + "file" + java.io.File.separator); } @Bean diff --git a/Zhibo/zhibo-h/crmeb-front/src/main/java/com/zbkj/front/controller/CommunityFrontController.java b/Zhibo/zhibo-h/crmeb-front/src/main/java/com/zbkj/front/controller/CommunityFrontController.java index 02c4e8c5..6b6ae035 100644 --- a/Zhibo/zhibo-h/crmeb-front/src/main/java/com/zbkj/front/controller/CommunityFrontController.java +++ b/Zhibo/zhibo-h/crmeb-front/src/main/java/com/zbkj/front/controller/CommunityFrontController.java @@ -70,6 +70,26 @@ public class CommunityFrontController { return CommonResult.success(result); } + @ApiOperation("获取附近用户(简化版 - 只返回头像、昵称、地址)") + @GetMapping("/nearby-users-simple") + public CommonResult> getNearbyUsersSimple( + @RequestParam(defaultValue = "100") Integer limit) { + log.info("=== 收到获取附近用户请求 - limit: {} ===", limit); + List users = communityService.getNearbyUsersSimple(limit); + java.util.Map result = new java.util.HashMap<>(); + result.put("list", users); + result.put("total", users.size()); + log.info("=== 返回用户数量: {} ===", users.size()); + return CommonResult.success(result); + } + + @ApiOperation("测试接口 - 验证Controller是否加载") + @GetMapping("/test") + public CommonResult test() { + log.info("=== 测试接口被调用 ==="); + return CommonResult.success("CommunityFrontController is working!"); + } + @ApiOperation("获取可匹配用户总数") @GetMapping("/user-count") public CommonResult getMatchableUserCount() { diff --git a/Zhibo/zhibo-h/crmeb-front/src/main/java/com/zbkj/front/controller/UserController.java b/Zhibo/zhibo-h/crmeb-front/src/main/java/com/zbkj/front/controller/UserController.java index 68559f16..3a5b7637 100644 --- a/Zhibo/zhibo-h/crmeb-front/src/main/java/com/zbkj/front/controller/UserController.java +++ b/Zhibo/zhibo-h/crmeb-front/src/main/java/com/zbkj/front/controller/UserController.java @@ -11,10 +11,12 @@ import com.zbkj.common.request.*; import com.zbkj.common.response.*; import com.zbkj.common.result.CommonResult; import com.zbkj.common.token.FrontTokenComponent; +import com.zbkj.common.vo.FileResultVo; import com.zbkj.front.service.UserCenterService; import com.zbkj.service.service.SystemGroupDataService; import com.zbkj.service.service.UserService; import com.zbkj.service.service.FollowRecordService; +import com.zbkj.service.service.UploadService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiOperation; @@ -23,6 +25,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; import java.math.BigDecimal; import java.util.HashMap; @@ -65,6 +68,9 @@ public class UserController { @Autowired private FollowRecordService followRecordService; + @Autowired + private UploadService uploadService; + /** * 获取用户/主播主页信息 * 主播:显示作品、关注、粉丝、获赞、粉丝团 @@ -265,6 +271,113 @@ public class UserController { } } + /** + * 修改个人资料(支持文件上传) + */ + @ApiOperation(value = "修改个人资料(支持文件上传)") + @RequestMapping(value = "/user/edit/multipart", method = RequestMethod.POST, consumes = "multipart/form-data") + public CommonResult personInfoWithFile( + @RequestParam(value = "avatarFile", required = false) MultipartFile avatarFile, + @RequestParam("nickname") String nickname, + @RequestParam(value = "mark", required = false) String mark, + @RequestParam(value = "birthday", required = false) String birthday, + @RequestParam(value = "sex", required = false) Integer sex, + @RequestParam(value = "addres", required = false) String addres) { + + System.out.println("========================================"); + System.out.println("========== 收到修改个人资料请求(带文件)=========="); + System.out.println("昵称: " + nickname); + System.out.println("个人签名: " + mark); + System.out.println("生日: " + birthday); + System.out.println("性别: " + sex); + System.out.println("所在地: " + addres); + System.out.println("是否有头像文件: " + (avatarFile != null && !avatarFile.isEmpty())); + + log.info("========== 收到修改个人资料请求(带文件)=========="); + log.info("昵称: {}", nickname); + log.info("个人签名: {}", mark); + log.info("生日: {}", birthday); + log.info("性别: {}", sex); + log.info("所在地: {}", addres); + log.info("是否有头像文件: {}", avatarFile != null && !avatarFile.isEmpty()); + + if (avatarFile != null && !avatarFile.isEmpty()) { + System.out.println("头像文件名: " + avatarFile.getOriginalFilename()); + System.out.println("头像文件大小: " + avatarFile.getSize() + " bytes"); + System.out.println("头像文件类型: " + avatarFile.getContentType()); + + log.info("头像文件名: {}", avatarFile.getOriginalFilename()); + log.info("头像文件大小: {} bytes", avatarFile.getSize()); + log.info("头像文件类型: {}", avatarFile.getContentType()); + } + + try { + UserEditRequest request = new UserEditRequest(); + request.setNickname(nickname); + request.setMark(mark); + request.setBirthday(birthday); + request.setSex(sex); + request.setAddres(addres); + + // 如果有头像文件,先上传 + if (avatarFile != null && !avatarFile.isEmpty()) { + System.out.println("========== 开始上传头像文件 =========="); + log.info("========== 开始上传头像文件 =========="); + try { + FileResultVo fileResult = uploadService.imageUpload(avatarFile, "avatar", 7); + String avatarUrl = fileResult.getUrl(); + System.out.println("头像上传成功: " + avatarUrl); + log.info("头像上传成功: {}", avatarUrl); + request.setAvatar(avatarUrl); + } catch (Exception e) { + System.out.println("========== 头像上传失败 =========="); + System.out.println("异常类型: " + e.getClass().getName()); + System.out.println("异常消息: " + e.getMessage()); + e.printStackTrace(); + + log.error("========== 头像上传失败 ==========", e); + log.error("异常类型: {}", e.getClass().getName()); + log.error("异常消息: {}", e.getMessage()); + if (e.getCause() != null) { + log.error("原因: {}", e.getCause().getMessage()); + } + return CommonResult.failed("头像上传失败:" + e.getMessage()); + } + } + + System.out.println("========== 开始更新用户资料 =========="); + log.info("========== 开始更新用户资料 =========="); + boolean result = userService.editUser(request); + System.out.println("修改结果: " + result); + log.info("修改结果: {}", result); + + if (result) { + System.out.println("========== 修改成功 =========="); + System.out.println("========================================"); + log.info("========== 修改成功 =========="); + return CommonResult.success(); + } + System.out.println("========== 修改失败:updateById返回false =========="); + System.out.println("========================================"); + log.error("========== 修改失败:updateById返回false =========="); + return CommonResult.failed("保存失败"); + } catch (Exception e) { + System.out.println("========== 修改个人资料异常 =========="); + System.out.println("异常类型: " + e.getClass().getName()); + System.out.println("异常消息: " + e.getMessage()); + e.printStackTrace(); + System.out.println("========================================"); + + log.error("========== 修改个人资料异常 ==========", e); + log.error("异常类型: {}", e.getClass().getName()); + log.error("异常消息: {}", e.getMessage()); + if (e.getCause() != null) { + log.error("原因: {}", e.getCause().getMessage()); + } + return CommonResult.failed("修改失败:" + e.getMessage()); + } + } + /** * 个人中心-用户信息 */ diff --git a/Zhibo/zhibo-h/crmeb-front/src/main/resources/logback-spring.xml b/Zhibo/zhibo-h/crmeb-front/src/main/resources/logback-spring.xml index 42efa5da..82149b37 100644 --- a/Zhibo/zhibo-h/crmeb-front/src/main/resources/logback-spring.xml +++ b/Zhibo/zhibo-h/crmeb-front/src/main/resources/logback-spring.xml @@ -27,23 +27,9 @@ - - - - - - - { - "app": "${APP_NAME}", - "timestamp":"%d{yyyy-MM-dd HH:mm:ss.SSS}", - "level": "%level", - "thread": "%thread", - "class": "%logger{40}", - "message": "%msg" } - %n - - + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n utf-8 diff --git a/Zhibo/zhibo-h/crmeb-service/src/main/java/com/zbkj/service/service/CommunityService.java b/Zhibo/zhibo-h/crmeb-service/src/main/java/com/zbkj/service/service/CommunityService.java index 5f388415..076baa2a 100644 --- a/Zhibo/zhibo-h/crmeb-service/src/main/java/com/zbkj/service/service/CommunityService.java +++ b/Zhibo/zhibo-h/crmeb-service/src/main/java/com/zbkj/service/service/CommunityService.java @@ -89,6 +89,11 @@ public interface CommunityService { */ List getNearbyUsers(Double latitude, Double longitude, Integer radius, Integer limit); + /** + * 获取附近用户(简化版 - 只返回头像、昵称、地址) + */ + List getNearbyUsersSimple(Integer limit); + /** * 获取可匹配用户总数(排除当前用户) */ diff --git a/Zhibo/zhibo-h/crmeb-service/src/main/java/com/zbkj/service/service/GiftRecordService.java b/Zhibo/zhibo-h/crmeb-service/src/main/java/com/zbkj/service/service/GiftRecordService.java index 8ef750a3..756cbbe0 100644 --- a/Zhibo/zhibo-h/crmeb-service/src/main/java/com/zbkj/service/service/GiftRecordService.java +++ b/Zhibo/zhibo-h/crmeb-service/src/main/java/com/zbkj/service/service/GiftRecordService.java @@ -2,10 +2,13 @@ package com.zbkj.service.service; import com.baomidou.mybatisplus.extension.service.IService; import com.zbkj.common.model.gift.GiftRecord; +import com.zbkj.common.page.CommonPage; +import com.zbkj.common.request.GiftRecordSearchRequest; import com.zbkj.common.request.SendGiftRequest; import com.zbkj.common.response.SendGiftResponse; import java.util.List; +import java.util.Map; /** * 礼物赠送记录服务接口 @@ -39,4 +42,17 @@ public interface GiftRecordService extends IService { * @return 总消费钻石数 */ Long getTotalDiamondByUser(Integer roomId, Integer userId); + + /** + * 分页查询礼物记录(带用户信息) + * @param request 搜索请求 + * @return 分页结果 + */ + CommonPage> getGiftRecordList(GiftRecordSearchRequest request); + + /** + * 获取礼物记录统计数据 + * @return 统计数据 + */ + Map getGiftRecordStatistics(); } diff --git a/Zhibo/zhibo-h/crmeb-service/src/main/java/com/zbkj/service/service/GiftService.java b/Zhibo/zhibo-h/crmeb-service/src/main/java/com/zbkj/service/service/GiftService.java index 8ab075be..89442b2c 100644 --- a/Zhibo/zhibo-h/crmeb-service/src/main/java/com/zbkj/service/service/GiftService.java +++ b/Zhibo/zhibo-h/crmeb-service/src/main/java/com/zbkj/service/service/GiftService.java @@ -2,8 +2,11 @@ package com.zbkj.service.service; import com.baomidou.mybatisplus.extension.service.IService; import com.zbkj.common.model.gift.Gift; +import com.zbkj.common.page.CommonPage; +import com.zbkj.common.request.GiftSearchRequest; import java.util.List; +import java.util.Map; /** * 礼物服务接口 @@ -19,4 +22,29 @@ public interface GiftService extends IService { * 根据ID获取礼物 */ Gift getGiftById(Integer giftId); + + /** + * 分页查询礼物列表 + */ + CommonPage getGiftList(GiftSearchRequest request); + + /** + * 更新礼物状态 + */ + boolean updateStatus(Integer id, Integer status); + + /** + * 批量删除礼物(逻辑删除) + */ + boolean deleteGifts(List ids); + + /** + * 获取礼物统计数据 + */ + Map getGiftStatistics(); + + /** + * 获取礼物记录统计数据 + */ + Map getGiftRecordStatistics(); } diff --git a/Zhibo/zhibo-h/crmeb-service/src/main/java/com/zbkj/service/service/impl/CommunityServiceImpl.java b/Zhibo/zhibo-h/crmeb-service/src/main/java/com/zbkj/service/service/impl/CommunityServiceImpl.java index 40921f88..04a769e6 100644 --- a/Zhibo/zhibo-h/crmeb-service/src/main/java/com/zbkj/service/service/impl/CommunityServiceImpl.java +++ b/Zhibo/zhibo-h/crmeb-service/src/main/java/com/zbkj/service/service/impl/CommunityServiceImpl.java @@ -17,6 +17,9 @@ import com.zbkj.service.dao.CommunityMatchConfigDao; import com.zbkj.service.dao.CommunityMessageDao; import com.zbkj.service.service.CommunityService; import com.zbkj.service.service.UserService; +import lombok.extern.slf4j.Slf4j; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -30,6 +33,8 @@ import java.util.List; @Service public class CommunityServiceImpl implements CommunityService { + private static final Logger log = LoggerFactory.getLogger(CommunityServiceImpl.class); + @Autowired private CommunityCategoryDao categoryDao; @@ -213,10 +218,11 @@ public class CommunityServiceImpl implements CommunityService { // 处理地址:优先使用addres字段,否则使用country String location = user.getAddres(); - if (location == null || location.isEmpty()) { + if (location == null || location.isEmpty() || "CN".equals(location)) { location = user.getCountry(); } - if (location == null || location.isEmpty()) { + // 如果country也是CN或为空,显示"附近" + if (location == null || location.isEmpty() || "CN".equals(location) || "China".equals(location)) { location = "附近"; } // 简化地址显示(只取城市部分) @@ -291,4 +297,164 @@ public class CommunityServiceImpl implements CommunityService { message.setAuditRemark("自动审核通过"); message.setAuditType(0); } + + // ==================== 获取附近用户(简化版) ==================== + @Override + public List getNearbyUsersSimple(Integer limit) { + log.info("获取附近用户(简化版) - limit: {}", limit); + + // 获取当前登录用户ID和位置信息 - 必须登录 + Integer currentUserId = null; + User currentUser = null; + try { + currentUserId = userService.getUserIdException(); + currentUser = userService.getById(currentUserId); + log.info("当前登录用户ID: {}, 位置: {}", currentUserId, currentUser != null ? currentUser.getAddres() : "未设置"); + } catch (Exception e) { + // 未登录用户,抛出异常 + log.warn("未登录用户尝试访问附近用户接口"); + throw new CrmebException("请先登录后再查看附近的人"); + } + + // 构建查询条件 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(User::getStatus, true); // 只查询正常状态的用户 + if (currentUserId != null) { + wrapper.ne(User::getUid, currentUserId); // 排除当前用户 + } + wrapper.last("ORDER BY RAND() LIMIT " + limit); // 随机获取 + + List users = userService.list(wrapper); + log.info("从数据库查询到用户数量: {}", users.size()); + + List result = new ArrayList<>(); + + for (User user : users) { + com.zbkj.common.vo.NearbyUserSimpleVO vo = new com.zbkj.common.vo.NearbyUserSimpleVO(); + vo.setId(user.getUid()); + vo.setNickname(user.getNickname() != null ? user.getNickname() : "用户" + user.getUid()); + vo.setAvatar(user.getAvatar()); + + // 处理地址 + String address = user.getAddres(); + if (address == null || address.isEmpty() || "CN".equals(address)) { + address = "未设置位置"; + } + vo.setAddress(address); + + // 计算距离 - 只使用真实数据,不生成随机距离 + if (currentUser != null && currentUser.getLatitude() != null && currentUser.getLongitude() != null + && user.getLatitude() != null && user.getLongitude() != null) { + // 使用真实经纬度计算距离(Haversine公式) + double distance = calculateDistanceByCoordinates( + currentUser.getLatitude().doubleValue(), + currentUser.getLongitude().doubleValue(), + user.getLatitude().doubleValue(), + user.getLongitude().doubleValue() + ); + vo.setDistance(distance); + vo.setDistanceText(formatDistance(distance)); + log.debug("用户 {} 真实距离: {} (基于经纬度)", user.getNickname(), vo.getDistanceText()); + } else { + // 如果没有位置信息,不显示距离 + vo.setDistance(null); + vo.setDistanceText(null); + log.debug("用户 {} 没有位置信息,不显示距离", user.getNickname()); + } + + result.add(vo); + } + + log.info("返回用户数量: {}", result.size()); + + // 按距离升序排序(从近到远) + result.sort((u1, u2) -> { + if (u1.getDistance() == null && u2.getDistance() == null) return 0; + if (u1.getDistance() == null) return 1; + if (u2.getDistance() == null) return -1; + return Double.compare(u1.getDistance(), u2.getDistance()); + }); + + log.info("已按距离排序,最近: {}km, 最远: {}km", + result.isEmpty() ? 0 : result.get(0).getDistance(), + result.isEmpty() ? 0 : result.get(result.size() - 1).getDistance()); + + return result; + } + + /** + * 使用Haversine公式计算两个经纬度之间的距离(公里) + * 这是真实的地球表面距离计算 + */ + private double calculateDistanceByCoordinates(double lat1, double lon1, double lat2, double lon2) { + final double EARTH_RADIUS = 6371.0; // 地球半径(公里) + + // 将角度转换为弧度 + double lat1Rad = Math.toRadians(lat1); + double lat2Rad = Math.toRadians(lat2); + double deltaLat = Math.toRadians(lat2 - lat1); + double deltaLon = Math.toRadians(lon2 - lon1); + + // Haversine公式 + double a = Math.sin(deltaLat / 2) * Math.sin(deltaLat / 2) + + Math.cos(lat1Rad) * Math.cos(lat2Rad) * + Math.sin(deltaLon / 2) * Math.sin(deltaLon / 2); + double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + + return EARTH_RADIUS * c; + } + + /** + * 计算两个地址之间的距离(简化版,基于地址估算) + * 根据省份和城市判断距离 + */ + private double calculateDistanceByAddress(String address1, String address2) { + if (address1 == null || address2 == null) { + return 0.5 + Math.random() * 9.5; // 随机0.5-10km + } + + // 解析地址 + String[] parts1 = address1.split("-"); + String[] parts2 = address2.split("-"); + + String province1 = parts1.length > 0 ? parts1[0] : address1; + String city1 = parts1.length > 1 ? parts1[1] : ""; + + String province2 = parts2.length > 0 ? parts2[0] : address2; + String city2 = parts2.length > 1 ? parts2[1] : ""; + + // 如果省份相同 + if (province1.equals(province2)) { + // 如果城市也相同 + if (!city1.isEmpty() && !city2.isEmpty() && city1.equals(city2)) { + // 同城:0.5-5km + return 0.5 + Math.random() * 4.5; + } else { + // 同省不同城:50-200km + return 50 + Math.random() * 150; + } + } else { + // 不同省:200-1000km + return 200 + Math.random() * 800; + } + } + + /** + * 格式化距离显示 + */ + private String formatDistance(double distance) { + if (distance < 1) { + // 小于1km,显示米 + return String.format("%.0fm", distance * 1000); + } else if (distance < 10) { + // 1-10km,保留1位小数 + return String.format("%.1fkm", distance); + } else if (distance < 100) { + // 10-100km,保留整数 + return String.format("%.0fkm", distance); + } else { + // 大于100km,显示整数 + return String.format("%.0fkm", distance); + } + } } diff --git a/Zhibo/zhibo-h/crmeb-service/src/main/java/com/zbkj/service/service/impl/GiftRecordServiceImpl.java b/Zhibo/zhibo-h/crmeb-service/src/main/java/com/zbkj/service/service/impl/GiftRecordServiceImpl.java index 66f659df..f8864f95 100644 --- a/Zhibo/zhibo-h/crmeb-service/src/main/java/com/zbkj/service/service/impl/GiftRecordServiceImpl.java +++ b/Zhibo/zhibo-h/crmeb-service/src/main/java/com/zbkj/service/service/impl/GiftRecordServiceImpl.java @@ -1,10 +1,14 @@ package com.zbkj.service.service.impl; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.zbkj.common.exception.CrmebException; import com.zbkj.common.model.gift.Gift; import com.zbkj.common.model.gift.GiftRecord; import com.zbkj.common.model.user.User; +import com.zbkj.common.page.CommonPage; +import com.zbkj.common.request.GiftRecordSearchRequest; import com.zbkj.common.request.SendGiftRequest; import com.zbkj.common.response.SendGiftResponse; import com.zbkj.service.dao.GiftRecordDao; @@ -12,13 +16,14 @@ import com.zbkj.service.service.GiftRecordService; import com.zbkj.service.service.GiftService; import com.zbkj.service.service.UserService; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; -import java.util.Date; -import java.util.List; +import java.util.*; +import java.util.stream.Collectors; /** * 礼物赠送记录服务实现 @@ -129,4 +134,112 @@ public class GiftRecordServiceImpl extends ServiceImpl> getGiftRecordList(GiftRecordSearchRequest request) { + Page pageParam = new Page<>(request.getPage(), request.getLimit()); + + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + + // 关键字搜索(送礼人/收礼人/礼物名称) + if (StringUtils.isNotBlank(request.getKeyword())) { + wrapper.and(w -> w + .like(GiftRecord::getSenderNickname, request.getKeyword()) + .or() + .like(GiftRecord::getReceiverNickname, request.getKeyword()) + .or() + .like(GiftRecord::getGiftName, request.getKeyword()) + ); + } + + // 日期范围查询 + if (StringUtils.isNotBlank(request.getStartDate())) { + wrapper.ge(GiftRecord::getCreateTime, request.getStartDate() + " 00:00:00"); + } + if (StringUtils.isNotBlank(request.getEndDate())) { + wrapper.le(GiftRecord::getCreateTime, request.getEndDate() + " 23:59:59"); + } + + // 按创建时间倒序 + wrapper.orderByDesc(GiftRecord::getCreateTime); + + Page pageResult = page(pageParam, wrapper); + + // 转换为 Map 格式(包含用户信息) + List> resultList = pageResult.getRecords().stream().map(record -> { + Map map = new HashMap<>(); + map.put("id", record.getId()); + map.put("gift_name", record.getGiftName()); + map.put("gift_icon", record.getGiftImage()); + map.put("gift_price", record.getDiamondPrice()); + map.put("quantity", record.getGiftCount()); + map.put("total_price", record.getTotalDiamond()); + + // 送礼人信息 + map.put("sender_id", record.getSenderId()); + map.put("sender_nickname", record.getSenderNickname()); + map.put("sender_avatar", record.getSenderAvatar()); + + // 收礼人信息 + map.put("receiver_id", record.getReceiverId()); + map.put("receiver_nickname", record.getReceiverNickname()); + + // 直播间信息 + map.put("room_id", record.getRoomId()); + map.put("room_title", ""); // 如果需要房间标题,需要关联查询 + + map.put("is_anonymous", 0); + map.put("create_time", record.getCreateTime()); + + // 查询用户手机号(如果需要) + if (record.getSenderId() != null) { + User sender = userService.getById(record.getSenderId()); + map.put("sender_phone", sender != null ? sender.getPhone() : ""); + } + if (record.getReceiverId() != null) { + User receiver = userService.getById(record.getReceiverId()); + map.put("receiver_phone", receiver != null ? receiver.getPhone() : ""); + map.put("receiver_avatar", receiver != null ? receiver.getAvatar() : ""); + } + + return map; + }).collect(Collectors.toList()); + + return CommonPage.restPage(resultList, pageResult); + } + + @Override + public Map getGiftRecordStatistics() { + Map statistics = new HashMap<>(); + + // 总礼物记录数 + long totalCount = count(); + statistics.put("totalCount", totalCount); + + // 总价值 + List allRecords = list(); + BigDecimal totalValue = allRecords.stream() + .map(GiftRecord::getTotalDiamond) + .filter(value -> value != null) + .reduce(BigDecimal.ZERO, BigDecimal::add); + statistics.put("totalValue", totalValue); + + // 今日礼物数和今日价值 + String today = new java.text.SimpleDateFormat("yyyy-MM-dd").format(new Date()); + LambdaQueryWrapper todayWrapper = new LambdaQueryWrapper<>(); + todayWrapper.ge(GiftRecord::getCreateTime, today + " 00:00:00"); + todayWrapper.le(GiftRecord::getCreateTime, today + " 23:59:59"); + + long todayCount = count(todayWrapper); + statistics.put("todayCount", todayCount); + + List todayRecords = list(todayWrapper); + BigDecimal todayValue = todayRecords.stream() + .map(GiftRecord::getTotalDiamond) + .filter(value -> value != null) + .reduce(BigDecimal.ZERO, BigDecimal::add); + statistics.put("todayValue", todayValue); + + return statistics; + } } diff --git a/Zhibo/zhibo-h/crmeb-service/src/main/java/com/zbkj/service/service/impl/GiftServiceImpl.java b/Zhibo/zhibo-h/crmeb-service/src/main/java/com/zbkj/service/service/impl/GiftServiceImpl.java index c237c28b..ab0b12e1 100644 --- a/Zhibo/zhibo-h/crmeb-service/src/main/java/com/zbkj/service/service/impl/GiftServiceImpl.java +++ b/Zhibo/zhibo-h/crmeb-service/src/main/java/com/zbkj/service/service/impl/GiftServiceImpl.java @@ -1,13 +1,19 @@ package com.zbkj.service.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.zbkj.common.model.gift.Gift; +import com.zbkj.common.page.CommonPage; +import com.zbkj.common.request.GiftSearchRequest; import com.zbkj.service.dao.GiftDao; import com.zbkj.service.service.GiftService; +import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import java.util.List; +import java.util.Map; /** * 礼物服务实现 @@ -19,6 +25,7 @@ public class GiftServiceImpl extends ServiceImpl implements GiftS public List getActiveGifts() { LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.eq(Gift::getStatus, 1); + wrapper.orderByAsc(Gift::getSort, Gift::getId); return list(wrapper); } @@ -26,4 +33,84 @@ public class GiftServiceImpl extends ServiceImpl implements GiftS public Gift getGiftById(Integer giftId) { return getById(giftId); } + + @Override + public CommonPage getGiftList(GiftSearchRequest request) { + Page pageParam = new Page<>(request.getPage(), request.getLimit()); + + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + + // 名称模糊查询 + if (StringUtils.isNotBlank(request.getName())) { + wrapper.like(Gift::getName, request.getName()); + } + + // 状态查询 + if (request.getStatus() != null) { + wrapper.eq(Gift::getStatus, request.getStatus()); + } + + // 排序 + wrapper.orderByAsc(Gift::getSort, Gift::getId); + + Page pageResult = page(pageParam, wrapper); + + return CommonPage.restPage(pageResult); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public boolean updateStatus(Integer id, Integer status) { + Gift gift = getById(id); + if (gift == null) { + throw new RuntimeException("礼物不存在"); + } + gift.setStatus(status); + return updateById(gift); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public boolean deleteGifts(List ids) { + return removeByIds(ids); + } + + @Override + public Map getGiftStatistics() { + Map statistics = new java.util.HashMap<>(); + + // 礼物总数 + LambdaQueryWrapper totalWrapper = new LambdaQueryWrapper<>(); + long total = count(totalWrapper); + statistics.put("total", total); + + // 启用的礼物数 + LambdaQueryWrapper enabledWrapper = new LambdaQueryWrapper<>(); + enabledWrapper.eq(Gift::getStatus, 1); + long enabled = count(enabledWrapper); + statistics.put("enabled", enabled); + + // 禁用的礼物数 + LambdaQueryWrapper disabledWrapper = new LambdaQueryWrapper<>(); + disabledWrapper.eq(Gift::getStatus, 0); + long disabled = count(disabledWrapper); + statistics.put("disabled", disabled); + + // 总价值(所有礼物的钻石价格总和) + List allGifts = list(); + java.math.BigDecimal totalValue = allGifts.stream() + .map(Gift::getDiamondPrice) + .filter(price -> price != null) + .reduce(java.math.BigDecimal.ZERO, java.math.BigDecimal::add); + statistics.put("totalValue", totalValue); + + return statistics; + } + + @Override + public Map getGiftRecordStatistics() { + // 这个方法应该由 GiftRecordService 实现 + // 这里只是占位,实际调用会转发到 GiftRecordService + return new java.util.HashMap<>(); + } } diff --git a/Zhibo/zhibo-h/crmeb-service/src/main/java/com/zbkj/service/service/impl/UploadServiceImpl.java b/Zhibo/zhibo-h/crmeb-service/src/main/java/com/zbkj/service/service/impl/UploadServiceImpl.java index d2cb2e1e..ce2a9312 100644 --- a/Zhibo/zhibo-h/crmeb-service/src/main/java/com/zbkj/service/service/impl/UploadServiceImpl.java +++ b/Zhibo/zhibo-h/crmeb-service/src/main/java/com/zbkj/service/service/impl/UploadServiceImpl.java @@ -87,10 +87,33 @@ public class UploadServiceImpl implements UploadService { public FileResultVo imageUpload(MultipartFile multipartFile, String model, Integer pid) { FileResultVo fileResultVo = new FileResultVo(); try { + logger.info("=== 开始上传图片 ==="); + logger.info("文件名: {}", multipartFile.getOriginalFilename()); + logger.info("文件大小: {} bytes ({} KB)", multipartFile.getSize(), multipartFile.getSize() / 1024); + logger.info("内容类型: {}", multipartFile.getContentType()); + logger.info("模块: {}, PID: {}", model, pid); + logger.info("文件是否为空: {}", multipartFile.isEmpty()); + fileResultVo = commonUpload(multipartFile, model, pid, UploadConstants.UPLOAD_FILE_KEYWORD); + + logger.info("图片上传成功,URL: {}", fileResultVo.getUrl()); + logger.info("=================="); } catch (IOException e) { - logger.error("图片上传IO异常,{}", e.getMessage()); - throw new CrmebException("图片上传 IO异常"); + logger.error("=== 图片上传IO异常 ==="); + logger.error("文件名: {}", multipartFile.getOriginalFilename()); + logger.error("异常类型: {}", e.getClass().getName()); + logger.error("异常消息: {}", e.getMessage()); + logger.error("堆栈跟踪: ", e); + logger.error("==================="); + throw new CrmebException("图片上传 IO异常: " + e.getMessage()); + } catch (Exception e) { + logger.error("=== 图片上传异常 ==="); + logger.error("文件名: {}", multipartFile.getOriginalFilename()); + logger.error("异常类型: {}", e.getClass().getName()); + logger.error("异常消息: {}", e.getMessage()); + logger.error("堆栈跟踪: ", e); + logger.error("================="); + throw new CrmebException("图片上传失败: " + e.getMessage()); } return fileResultVo; } @@ -171,6 +194,9 @@ public class UploadServiceImpl implements UploadService { if (ObjectUtil.isNull(multipartFile) || multipartFile.isEmpty()) { throw new CrmebException(CommonResultCode.VALIDATE_FAILED, "上载的文件对象不存在..."); } + + logger.info("=== commonUpload 开始 ==="); + // 校验 String fileName = multipartFile.getOriginalFilename(); float fileSize = (float) multipartFile.getSize() / 1024 / 1024; @@ -182,19 +208,31 @@ public class UploadServiceImpl implements UploadService { // 服务器存储地址 - 使用绝对路径 String rootPath = crmebConfig.getAbsoluteImagePath(); + logger.info("根路径: {}", rootPath); + // 模块 String modelPath = "public/" + model + "/"; + logger.info("模块路径: {}", modelPath); + // 类型 String type = (fileType.equals(UploadConstants.UPLOAD_FILE_KEYWORD) ? UploadConstants.UPLOAD_FILE_KEYWORD : UploadConstants.UPLOAD_AFTER_FILE_KEYWORD) + "/"; + logger.info("类型路径: {}", type); // 变更文件名 String newFileName = UploadUtil.fileName(extName); + logger.info("新文件名: {}", newFileName); + // 创建目标文件的名称,规则: 子目录/年/月/日.后缀名 String webPath = type + modelPath + CrmebDateUtil.nowDate("yyyy/MM/dd") + "/"; + logger.info("Web路径: {}", webPath); + // 文件分隔符转化为当前系统的格式 String destPath = FilenameUtils.separatorsToSystem(rootPath + webPath) + newFileName; + logger.info("目标完整路径: {}", destPath); + // 创建文件 File file = UploadUtil.createFile(destPath); + logger.info("文件对象创建成功: {}", file.getAbsolutePath()); // 拼装返回的数据 FileResultVo resultFile = new FileResultVo(); @@ -221,16 +259,41 @@ public class UploadServiceImpl implements UploadService { //图片上传类型 1本地 2七牛云 3OSS 4COS, 默认本地 String uploadType = systemConfigService.getValueByKeyException(SysConfigConstants.CONFIG_UPLOAD_TYPE); Integer uploadTypeInt = Integer.parseInt(uploadType); + logger.info("上传类型: {}", uploadTypeInt); + if (uploadTypeInt.equals(1)) { // 保存文件 - multipartFile.transferTo(file); + logger.info("开始保存文件到本地: {}", file.getAbsolutePath()); + logger.info("文件父目录存在: {}", file.getParentFile().exists()); + logger.info("文件父目录可写: {}", file.getParentFile().canWrite()); + logger.info("文件是否已存在: {}", file.exists()); + + try { + multipartFile.transferTo(file); + logger.info("文件保存成功!"); + } catch (IOException e) { + logger.error("transferTo失败!", e); + logger.error("文件路径: {}", file.getAbsolutePath()); + logger.error("文件大小: {} bytes", multipartFile.getSize()); + logger.error("文件名: {}", multipartFile.getOriginalFilename()); + throw e; + } + systemAttachmentService.save(systemAttachment); return resultFile; } CloudVo cloudVo = new CloudVo(); // 判断是否保存本地 String fileIsSave = systemConfigService.getValueByKeyException(SysConfigConstants.CONFIG_FILE_IS_SAVE); - multipartFile.transferTo(file); + + logger.info("云存储模式,先保存到本地: {}", file.getAbsolutePath()); + try { + multipartFile.transferTo(file); + logger.info("本地文件保存成功!"); + } catch (IOException e) { + logger.error("云存储模式下本地保存失败!", e); + throw e; + } switch (uploadTypeInt) { case 2: systemAttachment.setImageType(2); diff --git a/Zhibo/zhibo-h/crmeb-service/src/main/java/com/zbkj/service/service/impl/UserServiceImpl.java b/Zhibo/zhibo-h/crmeb-service/src/main/java/com/zbkj/service/service/impl/UserServiceImpl.java index be41f90e..b5a310fc 100644 --- a/Zhibo/zhibo-h/crmeb-service/src/main/java/com/zbkj/service/service/impl/UserServiceImpl.java +++ b/Zhibo/zhibo-h/crmeb-service/src/main/java/com/zbkj/service/service/impl/UserServiceImpl.java @@ -1747,38 +1747,115 @@ public class UserServiceImpl extends ServiceImpl implements UserS */ @Override public Boolean editUser(UserEditRequest request) { - User user = getInfo(); + logger.info("========== editUser 开始 =========="); + logger.info("请求参数: {}", request); - // 更新头像(如果提供) - if (request.getAvatar() != null && !request.getAvatar().isEmpty()) { - user.setAvatar(systemAttachmentService.clearPrefix(request.getAvatar())); + User user = getInfo(); + if (user == null) { + logger.error("获取当前用户失败,用户未登录或token失效"); + throw new CrmebException("用户未登录或登录已过期"); } - // 更新昵称 - user.setNickname(request.getNickname()); + logger.info("当前用户ID: {}, 昵称: {}", user.getUid(), user.getNickname()); + + // 更新昵称(必填字段,需要验证) + if (request.getNickname() != null && !request.getNickname().trim().isEmpty()) { + logger.info("更新昵称: {} -> {}", user.getNickname(), request.getNickname()); + user.setNickname(request.getNickname().trim()); + } else { + logger.warn("昵称为空,保持原昵称: {}", user.getNickname()); + // 不更新昵称,保持原值 + } + + // 更新头像(如果提供) + if (request.getAvatar() != null && !request.getAvatar().trim().isEmpty()) { + String originalAvatar = request.getAvatar(); + String clearedAvatar = systemAttachmentService.clearPrefix(originalAvatar); + logger.info("头像URL处理: 原始={}, 处理后={}", originalAvatar, clearedAvatar); + + // 确保处理后的URL不为空 + if (clearedAvatar != null && !clearedAvatar.trim().isEmpty()) { + user.setAvatar(clearedAvatar); + } else { + logger.warn("头像URL处理后为空,保持原头像"); + } + } // 更新生日(如果提供) - if (request.getBirthday() != null && !request.getBirthday().isEmpty()) { + if (request.getBirthday() != null && !request.getBirthday().trim().isEmpty()) { + logger.info("更新生日: {}", request.getBirthday()); user.setBirthday(request.getBirthday()); } // 更新性别(如果提供) if (request.getSex() != null) { + logger.info("更新性别: {}", request.getSex()); user.setSex(request.getSex()); } // 更新地址(如果提供) - if (request.getAddres() != null) { + if (request.getAddres() != null && !request.getAddres().trim().isEmpty()) { + logger.info("更新地址: {}", request.getAddres()); user.setAddres(request.getAddres()); } + // 更新纬度(如果提供) + if (request.getLatitude() != null) { + logger.info("更新纬度: {}", request.getLatitude()); + user.setLatitude(request.getLatitude()); + } + + // 更新经度(如果提供) + if (request.getLongitude() != null) { + logger.info("更新经度: {}", request.getLongitude()); + user.setLongitude(request.getLongitude()); + } + + // 如果更新了地址或经纬度,更新位置更新时间 + if ((request.getAddres() != null && !request.getAddres().trim().isEmpty()) + || request.getLatitude() != null + || request.getLongitude() != null) { + user.setLocationUpdateTime(DateUtil.date()); + logger.info("更新位置更新时间: {}", user.getLocationUpdateTime()); + } + + // 更新经纬度(如果提供) + if (request.getLatitude() != null && request.getLongitude() != null) { + logger.info("更新经纬度: 纬度={}, 经度={}", request.getLatitude(), request.getLongitude()); + user.setLatitude(request.getLatitude()); + user.setLongitude(request.getLongitude()); + user.setLocationUpdateTime(DateUtil.date()); + } + // 更新个人签名/备注(如果提供) if (request.getMark() != null) { + logger.info("更新个人签名: {}", request.getMark()); user.setMark(request.getMark()); } user.setUpdateTime(DateUtil.date()); - return updateById(user); + + logger.info("准备更新数据库,用户ID: {}, 昵称: {}", user.getUid(), user.getNickname()); + + try { + boolean result = updateById(user); + logger.info("数据库更新结果: {}", result); + + if (!result) { + logger.error("========== 数据库更新失败 =========="); + logger.error("用户对象: {}", user); + logger.error("可能原因:1.数据库连接问题 2.字段约束问题 3.并发更新冲突"); + } else { + logger.info("========== editUser 成功 =========="); + } + + return result; + } catch (Exception e) { + logger.error("========== 数据库更新异常 ==========", e); + logger.error("异常类型: {}", e.getClass().getName()); + logger.error("异常消息: {}", e.getMessage()); + throw new CrmebException("更新用户信息失败: " + e.getMessage()); + } } /** diff --git a/android-app/app/src/main/AndroidManifest.xml b/android-app/app/src/main/AndroidManifest.xml index 30882bb0..abf66831 100644 --- a/android-app/app/src/main/AndroidManifest.xml +++ b/android-app/app/src/main/AndroidManifest.xml @@ -12,6 +12,9 @@ + + + diff --git a/android-app/app/src/main/java/com/example/livestreaming/EditProfileActivity.java b/android-app/app/src/main/java/com/example/livestreaming/EditProfileActivity.java index 1faa769e..7a63bfce 100644 --- a/android-app/app/src/main/java/com/example/livestreaming/EditProfileActivity.java +++ b/android-app/app/src/main/java/com/example/livestreaming/EditProfileActivity.java @@ -31,6 +31,7 @@ import androidx.recyclerview.widget.RecyclerView; import com.bumptech.glide.Glide; import com.example.livestreaming.databinding.ActivityEditProfileBinding; +import com.example.livestreaming.location.TianDiTuLocationService; import com.example.livestreaming.net.ApiClient; import com.example.livestreaming.net.ApiResponse; import com.example.livestreaming.net.ApiService; @@ -60,6 +61,7 @@ public class EditProfileActivity extends AppCompatActivity { private static final String TAG = "EditProfileActivity"; private ActivityEditProfileBinding binding; private ApiService apiService; + private TianDiTuLocationService locationService; private static final String PREFS_NAME = "profile_prefs"; private static final String KEY_NAME = "profile_name"; @@ -81,6 +83,11 @@ public class EditProfileActivity extends AppCompatActivity { private ActivityResultLauncher pickImageLauncher; private ActivityResultLauncher takePictureLauncher; private ActivityResultLauncher requestCameraPermissionLauncher; + private ActivityResultLauncher requestStoragePermissionLauncher; + private ActivityResultLauncher requestLocationPermissionLauncher; + + private boolean isPickingFromGallery = false; + private boolean isRequestingLocation = false; public static void start(Context context) { Intent intent = new Intent(context, EditProfileActivity.class); @@ -101,6 +108,7 @@ public class EditProfileActivity extends AppCompatActivity { setContentView(binding.getRoot()); apiService = ApiClient.getService(this); + locationService = new TianDiTuLocationService(this); pickImageLauncher = registerForActivityResult(new ActivityResultContracts.GetContent(), uri -> { if (uri == null) return; @@ -126,14 +134,44 @@ public class EditProfileActivity extends AppCompatActivity { if (pendingCameraUri == null) return; takePictureLauncher.launch(pendingCameraUri); }); + + // 存储权限请求(Android 13+需要READ_MEDIA_IMAGES) + requestStoragePermissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestPermission(), granted -> { + if (!granted) { + Toast.makeText(this, "需要存储权限才能选择图片", Toast.LENGTH_LONG).show(); + return; + } + if (isPickingFromGallery) { + pickImageLauncher.launch("image/*"); + } + }); + + // 位置权限请求 + requestLocationPermissionLauncher = registerForActivityResult( + new ActivityResultContracts.RequestMultiplePermissions(), + result -> { + Boolean fineLocation = result.get(Manifest.permission.ACCESS_FINE_LOCATION); + Boolean coarseLocation = result.get(Manifest.permission.ACCESS_COARSE_LOCATION); + + if ((fineLocation != null && fineLocation) || (coarseLocation != null && coarseLocation)) { + // 权限已授予,位置信息将由系统自动获取 + Toast.makeText(this, "位置权限已授予", Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(this, "需要位置权限才能自动获取所在地", Toast.LENGTH_LONG).show(); + } + } + ); binding.backButton.setOnClickListener(v -> finish()); binding.cancelButton.setOnClickListener(v -> finish()); loadFromPrefs(); setupGenderDropdown(); - setupLocationDropdown(); + // 所在地已删除,不再需要setupLocationDropdown setupBirthdayPicker(); + + // 不再在编辑资料页面自动获取位置 + // requestLocationAndUpdate(); binding.avatarRow.setOnClickListener(v -> showAvatarBottomSheet()); @@ -182,54 +220,58 @@ public class EditProfileActivity extends AppCompatActivity { String bio = binding.inputBio.getText() != null ? binding.inputBio.getText().toString().trim() : ""; String birthday = binding.inputBirthday.getText() != null ? binding.inputBirthday.getText().toString().trim() : ""; String gender = binding.inputGender.getText() != null ? binding.inputGender.getText().toString().trim() : ""; - String location = binding.inputLocation.getText() != null ? binding.inputLocation.getText().toString().trim() : ""; if (TextUtils.isEmpty(name)) { Toast.makeText(this, "昵称不能为空", Toast.LENGTH_SHORT).show(); return; } - // 如果选择了新头像,先上传头像 - if (selectedAvatarUri != null && uploadedAvatarUrl == null) { - isUploading = true; - binding.saveButton.setEnabled(false); - Toast.makeText(this, "正在上传头像...", Toast.LENGTH_SHORT).show(); - - uploadAvatar(selectedAvatarUri, avatarUrl -> { - uploadedAvatarUrl = avatarUrl; - // 头像上传成功后,更新用户资料 - updateUserProfile(name, bio, birthday, gender, location, avatarUrl); - }, error -> { - isUploading = false; - binding.saveButton.setEnabled(true); - Toast.makeText(this, "头像上传失败: " + error, Toast.LENGTH_SHORT).show(); - }); + isUploading = true; + binding.saveButton.setEnabled(false); + + // 检查是否有新选择的本地头像 + boolean hasNewAvatar = selectedAvatarUri != null && uploadedAvatarUrl == null; + if (hasNewAvatar) { + String scheme = selectedAvatarUri.getScheme(); + if ("http".equalsIgnoreCase(scheme) || "https".equalsIgnoreCase(scheme)) { + hasNewAvatar = false; // 网络URL不算新头像 + } + } + + if (hasNewAvatar) { + // 有新头像,使用multipart接口 + updateUserProfileWithFile(selectedAvatarUri, name, bio, birthday, gender); } else { - // 没有新头像,直接更新资料 + // 没有新头像,使用普通接口 String existingAvatarUrl = getSharedPreferences(PREFS_NAME, MODE_PRIVATE).getString(KEY_AVATAR_URL, null); - updateUserProfile(name, bio, birthday, gender, location, uploadedAvatarUrl != null ? uploadedAvatarUrl : existingAvatarUrl); + updateUserProfile(name, bio, birthday, gender, uploadedAvatarUrl != null ? uploadedAvatarUrl : existingAvatarUrl); } } /** - * 上传头像到服务器 + * 更新用户资料(带文件上传) */ - private void uploadAvatar(Uri imageUri, OnAvatarUploadSuccess onSuccess, OnAvatarUploadError onError) { + private void updateUserProfileWithFile(Uri avatarUri, String name, String bio, String birthday, String gender) { + InputStream inputStream = null; try { + Log.d(TAG, "开始上传头像并保存资料,URI: " + avatarUri); + // 获取文件名 - String fileName = getFileName(imageUri); + String fileName = getFileName(avatarUri); if (fileName == null) { fileName = "avatar_" + System.currentTimeMillis() + ".jpg"; } // 读取文件内容 - InputStream inputStream = getContentResolver().openInputStream(imageUri); + inputStream = getContentResolver().openInputStream(avatarUri); if (inputStream == null) { - onError.onError("无法读取图片文件"); + isUploading = false; + binding.saveButton.setEnabled(true); + Toast.makeText(this, "无法读取图片文件", Toast.LENGTH_SHORT).show(); return; } - // 读取所有字节 + // 读取所有字节到内存 java.io.ByteArrayOutputStream buffer = new java.io.ByteArrayOutputStream(); byte[] data = new byte[8192]; int nRead; @@ -238,48 +280,199 @@ public class EditProfileActivity extends AppCompatActivity { } buffer.flush(); byte[] fileBytes = buffer.toByteArray(); - inputStream.close(); + + // 创建请求体 + RequestBody requestFile = RequestBody.create(MediaType.parse("image/*"), fileBytes); + MultipartBody.Part avatarPart = MultipartBody.Part.createFormData("avatarFile", fileName, requestFile); + + RequestBody nicknamePart = RequestBody.create(MediaType.parse("text/plain"), name); + RequestBody markPart = RequestBody.create(MediaType.parse("text/plain"), bio != null ? bio : ""); + RequestBody birthdayPart = RequestBody.create(MediaType.parse("text/plain"), birthday != null ? birthday : ""); + + // 转换性别 + int sex = 0; + if ("男".equals(gender)) sex = 1; + else if ("女".equals(gender)) sex = 2; + else if ("保密".equals(gender)) sex = 3; + RequestBody sexPart = RequestBody.create(MediaType.parse("text/plain"), String.valueOf(sex)); + + // 注意:不再传递addres参数,位置信息由系统自动获取 + + // 调用新接口 + apiService.updateUserInfoWithFile(avatarPart, nicknamePart, markPart, birthdayPart, sexPart, null) + .enqueue(new Callback>() { + @Override + public void onResponse(Call> call, Response> response) { + isUploading = false; + binding.saveButton.setEnabled(true); + + if (response.isSuccessful() && response.body() != null && response.body().isOk()) { + Log.d(TAG, "保存成功"); + // 保存到本地 + saveToLocalPrefs(name, bio, birthday, gender, null); + Toast.makeText(EditProfileActivity.this, "保存成功", Toast.LENGTH_SHORT).show(); + setResult(RESULT_OK); + finish(); + } else { + String msg = response.body() != null ? response.body().getMessage() : "保存失败"; + Log.e(TAG, "保存失败: " + msg); + Toast.makeText(EditProfileActivity.this, "保存失败: " + msg, Toast.LENGTH_SHORT).show(); + } + } + + @Override + public void onFailure(Call> call, Throwable t) { + isUploading = false; + binding.saveButton.setEnabled(true); + Log.e(TAG, "网络错误", t); + Toast.makeText(EditProfileActivity.this, "网络错误: " + t.getMessage(), Toast.LENGTH_SHORT).show(); + } + }); + } catch (Exception e) { + isUploading = false; + binding.saveButton.setEnabled(true); + Log.e(TAG, "上传异常", e); + Toast.makeText(this, "上传异常: " + e.getMessage(), Toast.LENGTH_SHORT).show(); + } finally { + if (inputStream != null) { + try { + inputStream.close(); + } catch (Exception e) { + Log.e(TAG, "关闭输入流失败", e); + } + } + } + } + + /** + * 上传头像到服务器 + */ + private void uploadAvatar(Uri imageUri, OnAvatarUploadSuccess onSuccess, OnAvatarUploadError onError) { + InputStream inputStream = null; + try { + Log.d(TAG, "开始上传头像,URI: " + imageUri); + + // 检查URI scheme,如果是网络URL则不能直接上传 + String scheme = imageUri.getScheme(); + if ("http".equalsIgnoreCase(scheme) || "https".equalsIgnoreCase(scheme)) { + Log.e(TAG, "不能上传网络URL,需要先下载或选择本地图片: " + imageUri); + onError.onError("请选择本地图片,不能上传网络图片"); + return; + } + + // 获取文件名 + String fileName = getFileName(imageUri); + if (fileName == null) { + fileName = "avatar_" + System.currentTimeMillis() + ".jpg"; + } + Log.d(TAG, "文件名: " + fileName); + + // 读取文件内容 - 使用ContentResolver确保兼容Android 10+ + inputStream = getContentResolver().openInputStream(imageUri); + if (inputStream == null) { + Log.e(TAG, "无法打开输入流"); + onError.onError("无法读取图片文件,请检查存储权限"); + return; + } + + // 读取所有字节到内存 + java.io.ByteArrayOutputStream buffer = new java.io.ByteArrayOutputStream(); + byte[] data = new byte[8192]; + int nRead; + long totalBytes = 0; + + while ((nRead = inputStream.read(data, 0, data.length)) != -1) { + buffer.write(data, 0, nRead); + totalBytes += nRead; + } + buffer.flush(); + byte[] fileBytes = buffer.toByteArray(); + + Log.d(TAG, "文件读取完成,大小: " + totalBytes + " 字节 (" + (totalBytes / 1024) + " KB)"); + + // 检查文件大小(限制10MB) + if (totalBytes > 10 * 1024 * 1024) { + onError.onError("图片文件过大,请选择小于10MB的图片"); + return; + } // 创建请求体 RequestBody requestFile = RequestBody.create(MediaType.parse("image/*"), fileBytes); MultipartBody.Part body = MultipartBody.Part.createFormData("file", fileName, requestFile); - RequestBody model = RequestBody.create(MediaType.parse("text/plain"), "user"); + RequestBody model = RequestBody.create(MediaType.parse("text/plain"), "avatar"); RequestBody pid = RequestBody.create(MediaType.parse("text/plain"), "7"); // 7表示前台用户 + Log.d(TAG, "开始调用上传接口..."); + // 调用上传接口 apiService.uploadImage(body, model, pid).enqueue(new Callback>() { @Override public void onResponse(Call> call, Response> response) { - if (response.isSuccessful() && response.body() != null && response.body().isOk()) { - FileUploadResponse data = response.body().getData(); - if (data != null && data.getUrl() != null) { - Log.d(TAG, "头像上传成功: " + data.getUrl()); - onSuccess.onSuccess(data.getUrl()); + Log.d(TAG, "上传接口响应,状态码: " + response.code()); + + if (response.isSuccessful() && response.body() != null) { + Log.d(TAG, "响应体: " + response.body()); + + if (response.body().isOk()) { + FileUploadResponse data = response.body().getData(); + if (data != null && data.getUrl() != null) { + Log.d(TAG, "头像上传成功: " + data.getUrl()); + onSuccess.onSuccess(data.getUrl()); + } else { + Log.e(TAG, "服务器返回数据为空"); + onError.onError("服务器返回数据异常"); + } } else { - onError.onError("服务器返回数据异常"); + String msg = response.body().getMessage(); + Log.e(TAG, "服务器返回错误: " + msg); + onError.onError(msg != null ? msg : "上传失败"); } } else { - String msg = response.body() != null ? response.body().getMessage() : "上传失败"; - onError.onError(msg); + String errorMsg = "HTTP " + response.code(); + try { + if (response.errorBody() != null) { + errorMsg += ": " + response.errorBody().string(); + } + } catch (Exception e) { + Log.e(TAG, "读取错误响应失败: " + e.getMessage()); + } + Log.e(TAG, "上传失败: " + errorMsg); + onError.onError("上传失败: " + errorMsg); } } @Override public void onFailure(Call> call, Throwable t) { - Log.e(TAG, "头像上传网络错误: " + t.getMessage()); - onError.onError("网络错误: " + t.getMessage()); + Log.e(TAG, "头像上传网络错误", t); + String errorMsg = t.getMessage(); + if (errorMsg == null) { + errorMsg = "网络连接失败"; + } + onError.onError("网络错误: " + errorMsg); } }); + } catch (java.io.IOException e) { + Log.e(TAG, "IO异常: " + e.getMessage(), e); + onError.onError("读取图片失败: " + e.getMessage() + "\n请检查存储权限或重新选择图片"); } catch (Exception e) { - Log.e(TAG, "头像上传异常: " + e.getMessage()); + Log.e(TAG, "上传异常", e); onError.onError("上传异常: " + e.getMessage()); + } finally { + // 确保关闭输入流 + if (inputStream != null) { + try { + inputStream.close(); + } catch (Exception e) { + Log.e(TAG, "关闭输入流失败", e); + } + } } } /** * 更新用户资料到服务器 */ - private void updateUserProfile(String name, String bio, String birthday, String gender, String location, String avatarUrl) { + private void updateUserProfile(String name, String bio, String birthday, String gender, String avatarUrl) { UserEditRequest request = new UserEditRequest(); request.setNickname(name); if (avatarUrl != null) { @@ -296,9 +489,7 @@ public class EditProfileActivity extends AppCompatActivity { else if ("保密".equals(gender)) sex = 3; request.setSex(sex); } - if (!TextUtils.isEmpty(location)) { - request.setAddres(location); - } + // 不再设置addres,由系统自动获取 if (!TextUtils.isEmpty(bio) && !BIO_HINT_TEXT.equals(bio)) { request.setMark(bio); } @@ -311,8 +502,8 @@ public class EditProfileActivity extends AppCompatActivity { if (response.isSuccessful() && response.body() != null && response.body().isOk()) { Log.d(TAG, "用户资料更新成功"); - // 保存到本地 - saveToLocalPrefs(name, bio, birthday, gender, location, avatarUrl); + // 保存到本地(不包含location) + saveToLocalPrefs(name, bio, birthday, gender, avatarUrl); Toast.makeText(EditProfileActivity.this, "保存成功", Toast.LENGTH_SHORT).show(); setResult(RESULT_OK); finish(); @@ -320,7 +511,7 @@ public class EditProfileActivity extends AppCompatActivity { String msg = response.body() != null ? response.body().getMessage() : "更新失败"; Log.e(TAG, "用户资料更新失败: " + msg); // 即使服务器更新失败,也保存到本地 - saveToLocalPrefs(name, bio, birthday, gender, location, avatarUrl); + saveToLocalPrefs(name, bio, birthday, gender, avatarUrl); Toast.makeText(EditProfileActivity.this, "已保存到本地,服务器同步失败: " + msg, Toast.LENGTH_SHORT).show(); setResult(RESULT_OK); finish(); @@ -333,7 +524,7 @@ public class EditProfileActivity extends AppCompatActivity { binding.saveButton.setEnabled(true); Log.e(TAG, "用户资料更新网络错误: " + t.getMessage()); // 网络失败也保存到本地 - saveToLocalPrefs(name, bio, birthday, gender, location, avatarUrl); + saveToLocalPrefs(name, bio, birthday, gender, avatarUrl); Toast.makeText(EditProfileActivity.this, "已保存到本地,网络同步失败", Toast.LENGTH_SHORT).show(); setResult(RESULT_OK); finish(); @@ -344,7 +535,7 @@ public class EditProfileActivity extends AppCompatActivity { /** * 保存到本地SharedPreferences */ - private void saveToLocalPrefs(String name, String bio, String birthday, String gender, String location, String avatarUrl) { + private void saveToLocalPrefs(String name, String bio, String birthday, String gender, String avatarUrl) { android.content.SharedPreferences.Editor editor = getSharedPreferences(PREFS_NAME, MODE_PRIVATE).edit(); editor.putString(KEY_NAME, name); @@ -367,11 +558,7 @@ public class EditProfileActivity extends AppCompatActivity { editor.putString(KEY_GENDER, gender); } - if (TextUtils.isEmpty(location)) { - editor.remove(KEY_LOCATION); - } else { - editor.putString(KEY_LOCATION, location); - } + // 不再保存location到本地,由系统自动获取 if (avatarUrl != null) { editor.putString(KEY_AVATAR_URL, avatarUrl); @@ -469,6 +656,7 @@ public class EditProfileActivity extends AppCompatActivity { String gender = getSharedPreferences(PREFS_NAME, MODE_PRIVATE).getString(KEY_GENDER, ""); String location = getSharedPreferences(PREFS_NAME, MODE_PRIVATE).getString(KEY_LOCATION, ""); String avatarUri = getSharedPreferences(PREFS_NAME, MODE_PRIVATE).getString(KEY_AVATAR_URI, null); + String avatarUrl = getSharedPreferences(PREFS_NAME, MODE_PRIVATE).getString(KEY_AVATAR_URL, null); binding.inputName.setText(name); if (!TextUtils.isEmpty(bio) && !BIO_HINT_TEXT.equals(bio)) { @@ -478,11 +666,23 @@ public class EditProfileActivity extends AppCompatActivity { } binding.inputBirthday.setText(birthday); binding.inputGender.setText(gender); - binding.inputLocation.setText(location); + // 不再加载location,已删除该输入框 + // 优先使用本地URI,如果没有则使用服务器URL if (!TextUtils.isEmpty(avatarUri)) { - selectedAvatarUri = Uri.parse(avatarUri); - Glide.with(this).load(selectedAvatarUri).circleCrop().into(binding.avatarPreview); + Uri uri = Uri.parse(avatarUri); + String scheme = uri.getScheme(); + // 只有本地URI才设置为selectedAvatarUri + if (!"http".equalsIgnoreCase(scheme) && !"https".equalsIgnoreCase(scheme)) { + selectedAvatarUri = uri; + Glide.with(this).load(selectedAvatarUri).circleCrop().into(binding.avatarPreview); + } else { + // 网络URL只用于显示,不设置为selectedAvatarUri + Glide.with(this).load(uri).circleCrop().into(binding.avatarPreview); + } + } else if (!TextUtils.isEmpty(avatarUrl)) { + // 使用服务器URL显示头像 + Glide.with(this).load(avatarUrl).circleCrop().into(binding.avatarPreview); } else { int avatarRes = getSharedPreferences(PREFS_NAME, MODE_PRIVATE).getInt(KEY_AVATAR_RES, 0); if (avatarRes != 0) { @@ -502,7 +702,8 @@ public class EditProfileActivity extends AppCompatActivity { pick.setOnClickListener(v -> { dialog.dismiss(); - pickImageLauncher.launch("image/*"); + isPickingFromGallery = true; + checkStoragePermissionAndPickImage(); }); camera.setOnClickListener(v -> { @@ -526,6 +727,35 @@ public class EditProfileActivity extends AppCompatActivity { } requestCameraPermissionLauncher.launch(Manifest.permission.CAMERA); } + + /** + * 检查存储权限并选择图片 + * Android 13+ (API 33+) 需要 READ_MEDIA_IMAGES + * Android 10-12 (API 29-32) 需要 READ_EXTERNAL_STORAGE + * Android 10以下直接访问 + */ + private void checkStoragePermissionAndPickImage() { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) { + // Android 13+ 需要 READ_MEDIA_IMAGES 权限 + if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_IMAGES) + == PackageManager.PERMISSION_GRANTED) { + pickImageLauncher.launch("image/*"); + } else { + requestStoragePermissionLauncher.launch(Manifest.permission.READ_MEDIA_IMAGES); + } + } else if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) { + // Android 10-12 使用分区存储,但仍需要 READ_EXTERNAL_STORAGE + if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) + == PackageManager.PERMISSION_GRANTED) { + pickImageLauncher.launch("image/*"); + } else { + requestStoragePermissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE); + } + } else { + // Android 10以下直接选择 + pickImageLauncher.launch("image/*"); + } + } private Uri createTempCameraUri() { try { @@ -557,9 +787,10 @@ public class EditProfileActivity extends AppCompatActivity { }); } - private void setupLocationDropdown() { - binding.inputLocation.setOnClickListener(v -> showLocationPicker()); - } + // 移除手动选择地址功能,将通过天地图API自动获取用户位置 + // private void setupLocationDropdown() { + // binding.inputLocation.setOnClickListener(v -> showLocationPicker()); + // } private void showLocationPicker() { BottomSheetDialog dialog = new BottomSheetDialog(this); @@ -575,8 +806,8 @@ public class EditProfileActivity extends AppCompatActivity { LocationDataManager locationManager = LocationDataManager.getInstance(); locationManager.loadData(this); - // 解析当前地址 - String currentLocation = binding.inputLocation.getText() != null ? binding.inputLocation.getText().toString() : ""; + // 解析当前地址(已移除手动输入,使用空字符串) + String currentLocation = ""; String[] parsed = locationManager.parseLocation(currentLocation); String currentProvince = parsed[0]; String currentCity = parsed[1]; @@ -631,8 +862,7 @@ public class EditProfileActivity extends AppCompatActivity { }); confirmButton.setOnClickListener(v -> { - String location = locationManager.formatLocation(selectedProvince[0], selectedCity[0]); - binding.inputLocation.setText(location); + // 位置信息已移除手动输入,此功能不再使用 dialog.dismiss(); }); @@ -788,4 +1018,3 @@ public class EditProfileActivity extends AppCompatActivity { return cal.getActualMaximum(Calendar.DAY_OF_MONTH); } } - diff --git a/android-app/app/src/main/java/com/example/livestreaming/MainActivity.java b/android-app/app/src/main/java/com/example/livestreaming/MainActivity.java index b2f29325..d48b72a4 100644 --- a/android-app/app/src/main/java/com/example/livestreaming/MainActivity.java +++ b/android-app/app/src/main/java/com/example/livestreaming/MainActivity.java @@ -55,8 +55,10 @@ import com.example.livestreaming.net.StreamConfig; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import retrofit2.Call; import retrofit2.Callback; @@ -74,6 +76,7 @@ public class MainActivity extends AppCompatActivity { private final List discoverRooms = new ArrayList<>(); // 发现页面的房间列表 private final List nearbyUsers = new ArrayList<>(); // 附近页面的用户列表 private NearbyUsersAdapter nearbyUsersAdapter; // 附近页面的适配器 + private boolean isLoadingMoreNearby = false; // 是否正在加载更多附近用户 // 新Tab布局相关 private RecommendUserAdapter recommendUserAdapter; // 关注页面推荐用户适配器 @@ -772,11 +775,8 @@ public class MainActivity extends AppCompatActivity { // 用户可能选择了"不再询问",提示去设置中开启 new AlertDialog.Builder(this) .setTitle("需要位置权限") - .setMessage("您已拒绝位置权限,如需使用附近功能,请在设置中手动开启位置权限。") .setPositiveButton("去设置", (dialog, which) -> { - Intent intent = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS); - intent.setData(android.net.Uri.parse("package:" + getPackageName())); - startActivity(intent); + openAppSettings(); }) .setNegativeButton("取消", null) .show(); @@ -2433,8 +2433,30 @@ public class MainActivity extends AppCompatActivity { // 设置附近内容RecyclerView RecyclerView nearbyContentList = findViewById(R.id.nearbyContentList); if (nearbyContentList != null && nearbyContentList.getAdapter() == null) { - nearbyContentList.setLayoutManager(new LinearLayoutManager(this)); + LinearLayoutManager layoutManager = new LinearLayoutManager(this); + nearbyContentList.setLayoutManager(layoutManager); nearbyContentList.setAdapter(nearbyUsersAdapter); + + // 添加滚动监听,实现加载更多 + nearbyContentList.addOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { + super.onScrolled(recyclerView, dx, dy); + + // 只在向下滚动时检测 + if (dy > 0) { + int visibleItemCount = layoutManager.getChildCount(); + int totalItemCount = layoutManager.getItemCount(); + int firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition(); + + // 当滚动到底部附近时加载更多(距离底部还有5个item时开始加载) + if ((visibleItemCount + firstVisibleItemPosition) >= totalItemCount - 5 + && firstVisibleItemPosition >= 0) { + loadMoreNearbyUsers(); + } + } + } + }); } // 设置附近页面的SwipeRefreshLayout @@ -3117,12 +3139,15 @@ public class MainActivity extends AppCompatActivity { }; handler.postDelayed(nearbyLoadingTimeoutRunnable, 3000); - // 从后端获取附近用户(获取更多用户以便随机筛选) - ApiClient.getService(getApplicationContext()).getNearbyUsers(0, 0, 50000, 50) - .enqueue(new Callback>() { + // 从后端获取附近用户(使用简化接口) + Log.d(TAG, "开始加载附近用户,调用接口: /api/front/community/nearby-users-simple"); + ApiClient.getService(getApplicationContext()).getNearbyUsersSimple(100) + .enqueue(new Callback>() { @Override - public void onResponse(Call> call, - Response> response) { + public void onResponse(Call> call, + Response> response) { + Log.d(TAG, "附近用户接口响应 - HTTP状态码: " + response.code()); + // 取消超时回调 if (nearbyLoadingTimeoutRunnable != null) { handler.removeCallbacks(nearbyLoadingTimeoutRunnable); @@ -3134,49 +3159,102 @@ public class MainActivity extends AppCompatActivity { nearbyLoadingContainer.setVisibility(View.GONE); } - if (response.isSuccessful() && response.body() != null && response.body().isOk()) { - CommunityResponse.NearbyUserList userList = response.body().getData(); - if (userList != null && userList.getUsers() != null && !userList.getUsers().isEmpty()) { - nearbyUsers.clear(); + if (response.isSuccessful() && response.body() != null) { + Log.d(TAG, "响应成功 - isOk: " + response.body().isOk() + ", code: " + response.body().getCode()); + + if (response.body().isOk()) { + CommunityResponse.NearbyUserSimpleList userList = response.body().getData(); + Log.d(TAG, "用户列表数据: " + (userList != null ? "不为空" : "为空")); - // 随机打乱用户列表 - List shuffledUsers = new ArrayList<>(userList.getUsers()); - Collections.shuffle(shuffledUsers); - - // 最多取10名用户 - int count = Math.min(10, shuffledUsers.size()); - for (int i = 0; i < count; i++) { - CommunityResponse.NearbyUser user = shuffledUsers.get(i); - String location = user.location != null ? user.location : ""; - nearbyUsers.add(new NearbyUser(String.valueOf(user.id), user.nickname, location, user.isOnline)); - } - - if (nearbyUsersAdapter != null) { - nearbyUsersAdapter.submitList(new ArrayList<>(nearbyUsers)); - } - - // 更新附近人数 - if (nearbyCountText != null) { - nearbyCountText.setText("共" + nearbyUsers.size() + "人"); - } - if (locationText != null) { - locationText.setText("已找到附近的人"); - } - - // 显示列表,隐藏空状态 - if (emptyNearbyContainer != null) { - emptyNearbyContainer.setVisibility(View.GONE); - } - if (nearbyList != null) { - nearbyList.setVisibility(View.VISIBLE); + if (userList != null && userList.getUsers() != null) { + Log.d(TAG, "用户数量: " + userList.getUsers().size()); + + if (!userList.getUsers().isEmpty()) { + nearbyUsers.clear(); + + // 不打乱顺序,保持后端返回的距离排序(从近到远) + List sortedUsers = userList.getUsers(); + + // 显示所有用户,不限制数量 + for (CommunityResponse.NearbyUserSimple user : sortedUsers) { + String address = user.address != null ? user.address : "未设置位置"; + String distanceText = user.distanceText != null ? user.distanceText : ""; + + // 调试日志:打印原始数据 + Log.d(TAG, "用户数据 - ID: " + user.id + ", 昵称: " + user.nickname); + Log.d(TAG, " 地址(address): " + user.address); + Log.d(TAG, " 距离(distanceText): " + user.distanceText); + + // 显示逻辑: + // 1. 如果有距离,显示:地址 · 距离 + // 2. 如果没有距离,只显示:地址 + String locationInfo; + if (!distanceText.isEmpty()) { + locationInfo = address + " · " + distanceText; + } else { + locationInfo = address; + } + Log.d(TAG, " 最终显示: " + locationInfo); + + nearbyUsers.add(new NearbyUser(String.valueOf(user.id), user.nickname, locationInfo, false)); + } + + if (nearbyUsersAdapter != null) { + nearbyUsersAdapter.submitList(new ArrayList<>(nearbyUsers)); + } + + // 更新附近人数 + if (nearbyCountText != null) { + nearbyCountText.setText("共" + nearbyUsers.size() + "人"); + } + if (locationText != null) { + locationText.setText("已找到附近的人"); + } + + // 显示列表,隐藏空状态 + if (emptyNearbyContainer != null) { + emptyNearbyContainer.setVisibility(View.GONE); + } + if (nearbyList != null) { + nearbyList.setVisibility(View.VISIBLE); + } + } else { + // 没有数据,显示空状态 + Log.w(TAG, "用户列表为空"); + if (nearbyCountText != null) { + nearbyCountText.setText(""); + } + if (locationText != null) { + locationText.setText("附近暂无用户"); + } + if (emptyNearbyContainer != null) { + emptyNearbyContainer.setVisibility(View.VISIBLE); + } + if (nearbyList != null) { + nearbyList.setVisibility(View.GONE); + } + } + } else { + // userList 为空 + Log.w(TAG, "用户列表数据为空"); + if (nearbyCountText != null) { + nearbyCountText.setText(""); + } + if (locationText != null) { + locationText.setText("附近暂无用户"); + } + if (emptyNearbyContainer != null) { + emptyNearbyContainer.setVisibility(View.VISIBLE); + } + if (nearbyList != null) { + nearbyList.setVisibility(View.GONE); + } } } else { - // 没有数据,显示空状态 - if (nearbyCountText != null) { - nearbyCountText.setText(""); - } + // 请求失败 + Log.e(TAG, "请求失败 - message: " + response.body().getMessage()); if (locationText != null) { - locationText.setText("附近暂无用户"); + locationText.setText("获取附近用户失败"); } if (emptyNearbyContainer != null) { emptyNearbyContainer.setVisibility(View.VISIBLE); @@ -3186,9 +3264,10 @@ public class MainActivity extends AppCompatActivity { } } } else { - // 请求失败 + // HTTP 请求失败 + Log.e(TAG, "HTTP请求失败 - 状态码: " + response.code()); if (locationText != null) { - locationText.setText("获取附近用户失败"); + locationText.setText("网络请求失败"); } if (emptyNearbyContainer != null) { emptyNearbyContainer.setVisibility(View.VISIBLE); @@ -3200,7 +3279,7 @@ public class MainActivity extends AppCompatActivity { } @Override - public void onFailure(Call> call, Throwable t) { + public void onFailure(Call> call, Throwable t) { Log.e(TAG, "加载附近用户失败: " + t.getMessage()); // 取消超时回调 if (nearbyLoadingTimeoutRunnable != null) { @@ -3225,6 +3304,71 @@ public class MainActivity extends AppCompatActivity { }); } + /** + * 加载更多附近用户 + */ + private void loadMoreNearbyUsers() { + // 防止重复加载 + if (isLoadingMoreNearby) { + return; + } + + isLoadingMoreNearby = true; + + // 从后端获取更多附近用户(使用简化接口) + ApiClient.getService(getApplicationContext()).getNearbyUsersSimple(50) + .enqueue(new Callback>() { + @Override + public void onResponse(Call> call, + Response> response) { + isLoadingMoreNearby = false; + + if (response.isSuccessful() && response.body() != null && response.body().isOk()) { + CommunityResponse.NearbyUserSimpleList userList = response.body().getData(); + if (userList != null && userList.getUsers() != null && !userList.getUsers().isEmpty()) { + // 随机打乱用户列表 + List shuffledUsers = new ArrayList<>(userList.getUsers()); + Collections.shuffle(shuffledUsers); + + // 获取当前已有的用户ID,避免重复 + Set existingIds = new HashSet<>(); + for (NearbyUser user : nearbyUsers) { + existingIds.add(user.getId()); + } + + // 添加新用户(过滤掉已存在的) + int addedCount = 0; + for (CommunityResponse.NearbyUserSimple user : shuffledUsers) { + String userId = String.valueOf(user.id); + if (!existingIds.contains(userId)) { + String address = user.address != null ? user.address : "未设置"; + nearbyUsers.add(new NearbyUser(userId, user.nickname, address, false)); + addedCount++; + } + } + + // 如果添加了新用户,更新列表 + if (addedCount > 0 && nearbyUsersAdapter != null) { + nearbyUsersAdapter.submitList(new ArrayList<>(nearbyUsers)); + + // 更新附近人数 + TextView nearbyCountText = findViewById(R.id.nearbyCountText); + if (nearbyCountText != null) { + nearbyCountText.setText("共" + nearbyUsers.size() + "人"); + } + } + } + } + } + + @Override + public void onFailure(Call> call, Throwable t) { + isLoadingMoreNearby = false; + Log.e(TAG, "加载更多附近用户失败: " + t.getMessage()); + } + }); + } + /** * 显示频道管理底部弹窗 */ @@ -3464,4 +3608,26 @@ public class MainActivity extends AppCompatActivity { } } } + + /** + * 打开应用设置页面,引导用户手动开启权限 + */ + private void openAppSettings() { + try { + Intent intent = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS); + intent.setData(android.net.Uri.parse("package:" + getPackageName())); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + } catch (Exception e) { + // 如果打开应用详情失败,尝试打开系统设置 + try { + Intent intent = new Intent(android.provider.Settings.ACTION_SETTINGS); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + Toast.makeText(this, "请在应用管理中找到本应用,然后点击权限,开启位置权限", Toast.LENGTH_LONG).show(); + } catch (Exception ex) { + Toast.makeText(this, "无法打开设置页面,请手动进入系统设置", Toast.LENGTH_SHORT).show(); + } + } + } } diff --git a/android-app/app/src/main/java/com/example/livestreaming/ProfileActivity.java b/android-app/app/src/main/java/com/example/livestreaming/ProfileActivity.java index 66bea021..2d254336 100644 --- a/android-app/app/src/main/java/com/example/livestreaming/ProfileActivity.java +++ b/android-app/app/src/main/java/com/example/livestreaming/ProfileActivity.java @@ -5,6 +5,7 @@ import android.content.Context; import android.content.Intent; import android.content.ClipData; import android.content.ClipboardManager; +import android.content.pm.PackageManager; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.net.Uri; @@ -17,6 +18,7 @@ import android.widget.Toast; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; import androidx.appcompat.app.AppCompatActivity; +import androidx.core.content.ContextCompat; import androidx.core.content.FileProvider; import androidx.appcompat.app.AlertDialog; import androidx.recyclerview.widget.GridLayoutManager; @@ -25,6 +27,7 @@ import com.bumptech.glide.Glide; import com.example.livestreaming.BuildConfig; import com.example.livestreaming.databinding.ActivityProfileBinding; import com.example.livestreaming.ShareUtils; +import com.example.livestreaming.location.TianDiTuLocationService; import com.example.livestreaming.net.ApiClient; import com.example.livestreaming.net.ApiResponse; import com.example.livestreaming.net.UserInfoResponse; @@ -48,6 +51,8 @@ public class ProfileActivity extends AppCompatActivity { private static final String TAG = "ProfileActivity"; private ActivityProfileBinding binding; + private TianDiTuLocationService locationService; + private ActivityResultLauncher requestLocationPermissionLauncher; private static final String PREFS_NAME = "profile_prefs"; private static final String KEY_NAME = "profile_name"; @@ -76,6 +81,23 @@ public class ProfileActivity extends AppCompatActivity { super.onCreate(savedInstanceState); binding = ActivityProfileBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); + + // 初始化定位服务 + locationService = new TianDiTuLocationService(this); + + // 注册位置权限请求 + requestLocationPermissionLauncher = registerForActivityResult( + new ActivityResultContracts.RequestMultiplePermissions(), + result -> { + Boolean fineLocation = result.get(Manifest.permission.ACCESS_FINE_LOCATION); + Boolean coarseLocation = result.get(Manifest.permission.ACCESS_COARSE_LOCATION); + + if ((fineLocation != null && fineLocation) || (coarseLocation != null && coarseLocation)) { + // 权限已授予,开始定位 + startLocationUpdate(); + } + } + ); // 注册编辑资料页面的结果监听 editProfileLauncher = registerForActivityResult( @@ -98,6 +120,9 @@ public class ProfileActivity extends AppCompatActivity { setupNavigationClicks(); setupWorksRecycler(); setupProfileTabs(); + + // 自动获取用户位置并更新IP归属地 + requestLocationAndUpdate(); BottomNavigationView bottomNavigation = binding.bottomNavInclude.bottomNavigation; bottomNavigation.setSelectedItemId(R.id.nav_profile); @@ -648,6 +673,9 @@ public class ProfileActivity extends AppCompatActivity { UnreadMessageManager.updateBadge(bottomNav); // 检查主播状态并显示/隐藏主播中心按钮 checkAndUpdateStreamerButton(); + + // 自动更新位置信息(每次进入页面时) + autoUpdateLocation(); } } @@ -998,4 +1026,293 @@ public class ProfileActivity extends AppCompatActivity { } }); } + + /** + * 请求位置权限并更新位置 + */ + private void requestLocationAndUpdate() { + Log.d(TAG, "========== 开始位置自动更新流程 =========="); + + // 检查是否已有位置权限 + if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) + == PackageManager.PERMISSION_GRANTED + || ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) + == PackageManager.PERMISSION_GRANTED) { + Log.d(TAG, "✅ 已有位置权限,直接开始定位"); + // 已有权限,直接开始定位 + startLocationUpdate(); + } else { + Log.d(TAG, "⚠️ 没有位置权限,请求权限"); + // 静默请求权限(不显示Toast) + requestLocationPermissionLauncher.launch(new String[]{ + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.ACCESS_COARSE_LOCATION + }); + } + } + + /** + * 开始定位并更新位置信息 + */ + private void startLocationUpdate() { + Log.d(TAG, "📍 调用天地图定位服务..."); + + locationService.startLocation(new TianDiTuLocationService.OnLocationResultListener() { + @Override + public void onSuccess(String province, String city, String address, double latitude, double longitude) { + Log.d(TAG, "✅ 定位成功 - 省份: " + province + ", 城市: " + city + ", 完整地址: " + address); + Log.d(TAG, "📍 经纬度 - 纬度: " + latitude + ", 经度: " + longitude); + + runOnUiThread(() -> { + // 格式化地址:省份-城市 + String location = ""; + if (!TextUtils.isEmpty(province) && !TextUtils.isEmpty(city)) { + // 如果城市名包含省份名,去掉省份 + if (city.startsWith(province)) { + location = city; + Log.d(TAG, "城市名包含省份名,只使用城市: " + location); + } else { + location = province + "-" + city; + Log.d(TAG, "格式化为省份-城市: " + location); + } + } else if (!TextUtils.isEmpty(province)) { + location = province; + Log.d(TAG, "只有省份: " + location); + } else if (!TextUtils.isEmpty(city)) { + location = city; + Log.d(TAG, "只有城市: " + location); + } + + if (!TextUtils.isEmpty(location)) { + Log.d(TAG, "💾 保存位置到本地: " + location); + + // 保存到本地 + getSharedPreferences(PREFS_NAME, MODE_PRIVATE) + .edit() + .putString(KEY_LOCATION, location) + .apply(); + + Log.d(TAG, "🔄 刷新页面显示"); + // 刷新显示 + loadAndDisplayTags(); + + Log.d(TAG, "🌐 准备更新到服务器数据库(包含经纬度)"); + // 更新到服务器数据库(包含经纬度) + updateLocationToServer(location, latitude, longitude); + + Log.d(TAG, "✅ IP归属地更新成功: " + location); + } else { + Log.w(TAG, "⚠️ 位置信息为空,跳过更新"); + } + }); + } + + @Override + public void onError(String error) { + // 静默失败,不显示错误提示 + Log.e(TAG, "❌ 位置获取失败: " + error); + } + }); + } + + /** + * 更新位置信息到服务器数据库(包含经纬度) + */ + private void updateLocationToServer(String location, double latitude, double longitude) { + if (!AuthHelper.isLoggedIn(this)) { + Log.w(TAG, "用户未登录,跳过位置更新"); + return; + } + + Log.d(TAG, "========== 开始更新位置到服务器 =========="); + Log.d(TAG, "准备更新位置到服务器: " + location); + Log.d(TAG, "经纬度: 纬度=" + latitude + ", 经度=" + longitude); + + // 创建更新请求 + com.example.livestreaming.net.UserEditRequest request = new com.example.livestreaming.net.UserEditRequest(); + request.setAddres(location); + request.setLatitude(latitude); + request.setLongitude(longitude); + + Log.d(TAG, "请求对象创建完成"); + Log.d(TAG, " - addres: " + request.getAddres()); + Log.d(TAG, " - latitude: " + request.getLatitude()); + Log.d(TAG, " - longitude: " + request.getLongitude()); + Log.d(TAG, " - nickname: " + request.getNickname()); + Log.d(TAG, "发送位置更新请求到: /api/front/user/edit"); + + ApiClient.getService(this).updateUserInfo(request) + .enqueue(new Callback>() { + @Override + public void onResponse(Call> call, Response> response) { + Log.d(TAG, "========== 收到服务器响应 =========="); + Log.d(TAG, "HTTP状态码: " + response.code()); + + if (response.isSuccessful() && response.body() != null) { + ApiResponse body = response.body(); + Log.d(TAG, "响应体不为空"); + Log.d(TAG, " - code: " + body.getCode()); + Log.d(TAG, " - message: " + body.getMessage()); + Log.d(TAG, " - data: " + body.getData()); + + if (body.isOk()) { + Log.d(TAG, "✅✅✅ 位置信息(含经纬度)已成功同步到服务器数据库 ✅✅✅"); + Log.d(TAG, "更新的位置: " + location); + Log.d(TAG, "更新的经纬度: " + latitude + ", " + longitude); + Toast.makeText(ProfileActivity.this, "位置已更新: " + location, Toast.LENGTH_SHORT).show(); + } else { + Log.e(TAG, "❌ 服务器返回错误"); + Log.e(TAG, "错误码: " + body.getCode()); + Log.e(TAG, "错误信息: " + body.getMessage()); + Toast.makeText(ProfileActivity.this, "位置更新失败: " + body.getMessage(), Toast.LENGTH_SHORT).show(); + } + } else { + Log.e(TAG, "❌ 响应不成功或响应体为空"); + Log.e(TAG, "HTTP状态码: " + response.code()); + + try { + if (response.errorBody() != null) { + String errorBody = response.errorBody().string(); + Log.e(TAG, "错误响应体: " + errorBody); + } + } catch (Exception e) { + Log.e(TAG, "读取错误响应失败", e); + } + } + + Log.d(TAG, "========== 位置更新流程结束 =========="); + } + + @Override + public void onFailure(Call> call, Throwable t) { + Log.e(TAG, "========== 网络请求失败 =========="); + Log.e(TAG, "错误类型: " + t.getClass().getName()); + Log.e(TAG, "错误消息: " + t.getMessage(), t); + Toast.makeText(ProfileActivity.this, "位置更新网络错误", Toast.LENGTH_SHORT).show(); + } + }); + } + + /** + * 自动更新位置信息(每次进入页面时调用) + * 静默更新,不显示任何提示 + */ + private void autoUpdateLocation() { + Log.d(TAG, "========== 自动更新位置开始 =========="); + + // 检查是否登录 + if (!AuthHelper.isLoggedIn(this)) { + Log.d(TAG, "用户未登录,跳过自动位置更新"); + return; + } + + // 检查位置权限 + if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) + != PackageManager.PERMISSION_GRANTED + && ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) + != PackageManager.PERMISSION_GRANTED) { + Log.d(TAG, "没有位置权限,跳过自动位置更新"); + return; + } + + Log.d(TAG, "开始自动获取位置..."); + + // 开始定位 + locationService.startLocation(new TianDiTuLocationService.OnLocationResultListener() { + @Override + public void onSuccess(String province, String city, String address, double latitude, double longitude) { + Log.d(TAG, "✅ 自动定位成功"); + Log.d(TAG, " 省份: " + province); + Log.d(TAG, " 城市: " + city); + Log.d(TAG, " 完整地址: " + address); + Log.d(TAG, " 纬度: " + latitude); + Log.d(TAG, " 经度: " + longitude); + + runOnUiThread(() -> { + // 格式化地址:省份-城市 + String location = ""; + if (!TextUtils.isEmpty(province) && !TextUtils.isEmpty(city)) { + if (city.startsWith(province)) { + location = city; + } else { + location = province + "-" + city; + } + } else if (!TextUtils.isEmpty(province)) { + location = province; + } else if (!TextUtils.isEmpty(city)) { + location = city; + } + + if (!TextUtils.isEmpty(location)) { + Log.d(TAG, "格式化后的位置: " + location); + + // 保存到本地 + getSharedPreferences(PREFS_NAME, MODE_PRIVATE) + .edit() + .putString(KEY_LOCATION, location) + .apply(); + + // 刷新显示 + loadAndDisplayTags(); + + // 静默更新到服务器(不显示Toast) + updateLocationToServerSilently(location, latitude, longitude); + + Log.d(TAG, "✅ 自动位置更新完成: " + location); + } else { + Log.w(TAG, "位置信息为空,跳过更新"); + } + }); + } + + @Override + public void onError(String error) { + // 静默失败,只记录日志 + Log.d(TAG, "自动定位失败(静默): " + error); + } + }); + } + + /** + * 静默更新位置到服务器(不显示Toast提示) + */ + private void updateLocationToServerSilently(String location, double latitude, double longitude) { + Log.d(TAG, "========== 静默更新位置到服务器 =========="); + Log.d(TAG, "位置: " + location); + Log.d(TAG, "经纬度: " + latitude + ", " + longitude); + + // 创建更新请求 + com.example.livestreaming.net.UserEditRequest request = new com.example.livestreaming.net.UserEditRequest(); + request.setAddres(location); + request.setLatitude(latitude); + request.setLongitude(longitude); + + ApiClient.getService(this).updateUserInfo(request) + .enqueue(new Callback>() { + @Override + public void onResponse(Call> call, Response> response) { + if (response.isSuccessful() && response.body() != null && response.body().isOk()) { + Log.d(TAG, "✅ 位置信息已静默更新到服务器"); + Log.d(TAG, " 地址: " + location); + Log.d(TAG, " 经纬度: " + latitude + ", " + longitude); + } else { + Log.w(TAG, "位置更新失败: " + (response.body() != null ? response.body().getMessage() : "未知错误")); + } + } + + @Override + public void onFailure(Call> call, Throwable t) { + Log.w(TAG, "位置更新网络错误: " + t.getMessage()); + } + }); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + // 停止定位服务 + if (locationService != null) { + locationService.stopLocation(); + } + } } diff --git a/android-app/app/src/main/java/com/example/livestreaming/SettingsPageActivity.java b/android-app/app/src/main/java/com/example/livestreaming/SettingsPageActivity.java index 501970bc..65d3a685 100644 --- a/android-app/app/src/main/java/com/example/livestreaming/SettingsPageActivity.java +++ b/android-app/app/src/main/java/com/example/livestreaming/SettingsPageActivity.java @@ -626,14 +626,15 @@ public class SettingsPageActivity extends AppCompatActivity { "• 麦克风:用于直播和语音聊天\n" + "• 位置:用于附近直播和发现功能\n" + "• 存储:用于保存图片和视频\n\n" + - "点击确定将跳转到系统设置页面,您可以在那里管理应用权限。") + "点击确定将跳转到应用信息页面,请在页面中找到\"权限\"选项来管理各项权限。") .setPositiveButton("前往设置", (d, w) -> { try { Intent intent = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS); intent.setData(android.net.Uri.parse("package:" + getPackageName())); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); } catch (Exception e) { - Toast.makeText(this, "无法打开设置页面", Toast.LENGTH_SHORT).show(); + Toast.makeText(this, "无法打开设置页面,请手动进入系统设置 > 应用管理", Toast.LENGTH_LONG).show(); } }) .setNegativeButton("取消", null) diff --git a/android-app/app/src/main/java/com/example/livestreaming/location/TianDiTuLocationService.java b/android-app/app/src/main/java/com/example/livestreaming/location/TianDiTuLocationService.java new file mode 100644 index 00000000..99ef0970 --- /dev/null +++ b/android-app/app/src/main/java/com/example/livestreaming/location/TianDiTuLocationService.java @@ -0,0 +1,360 @@ +package com.example.livestreaming.location; + +import android.Manifest; +import android.content.Context; +import android.content.pm.PackageManager; +import android.location.Location; +import android.location.LocationListener; +import android.location.LocationManager; +import android.os.Bundle; +import android.util.Log; + +import androidx.core.content.ContextCompat; + +import org.json.JSONObject; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLEncoder; + +/** + * 天地图定位服务 + * 使用Android原生定位获取经纬度,然后通过天地图API进行逆地理编码获取地址 + */ +public class TianDiTuLocationService { + + private static final String TAG = "TianDiTuLocation"; + + // 天地图API Key + private static final String TIANDITU_API_KEY = "1b337ad2535a95289c5b943858dd53ac"; + + // 天地图逆地理编码API + private static final String GEOCODE_API_URL = "http://api.tianditu.gov.cn/geocoder"; + + // 测试模式:如果为true,使用固定的中国坐标进行测试 + // 设置为false后,将使用真实的GPS定位 + // 注意:Android模拟器默认位置在美国,需要在模拟器中手动设置中国坐标 + // 或者开启测试模式进行开发测试 + private static final boolean TEST_MODE = false; // 关闭测试模式,使用真实GPS定位 + // private static final double TEST_LATITUDE = 22.5431; // 深圳纬度(仅测试模式使用) + // private static final double TEST_LONGITUDE = 114.0579; // 深圳经度(仅测试模式使用) + + private Context context; + private LocationManager locationManager; + private LocationListener locationListener; + private OnLocationResultListener resultListener; + + // 保存当前定位的经纬度 + private double currentLatitude; + private double currentLongitude; + + public interface OnLocationResultListener { + void onSuccess(String province, String city, String address, double latitude, double longitude); + void onError(String error); + } + + public TianDiTuLocationService(Context context) { + this.context = context.getApplicationContext(); + this.locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); + } + + /** + * 开始定位 + */ + public void startLocation(OnLocationResultListener listener) { + this.resultListener = listener; + + // 测试模式:直接使用固定坐标(已禁用) + // if (TEST_MODE) { + // Log.d(TAG, "【测试模式】使用固定坐标 - 纬度: " + TEST_LATITUDE + ", 经度: " + TEST_LONGITUDE); + // reverseGeocode(TEST_LONGITUDE, TEST_LATITUDE); + // return; + // } + + // 检查权限 + if (!checkLocationPermission()) { + if (resultListener != null) { + resultListener.onError("缺少定位权限"); + } + return; + } + + // 检查定位服务是否开启 + if (!isLocationEnabled()) { + if (resultListener != null) { + resultListener.onError("请先开启定位服务"); + } + return; + } + + try { + // 创建位置监听器 + locationListener = new LocationListener() { + @Override + public void onLocationChanged(Location location) { + double lat = location.getLatitude(); + double lon = location.getLongitude(); + Log.d(TAG, "位置更新 - 纬度(Latitude): " + lat + ", 经度(Longitude): " + lon); + + // 获取到位置后,停止监听 + stopLocation(); + // 进行逆地理编码 + reverseGeocode(lon, lat); + } + + @Override + public void onStatusChanged(String provider, int status, Bundle extras) { + } + + @Override + public void onProviderEnabled(String provider) { + } + + @Override + public void onProviderDisabled(String provider) { + } + }; + + // 优先使用GPS定位 + if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) { + locationManager.requestLocationUpdates( + LocationManager.GPS_PROVIDER, + 0, + 0, + locationListener + ); + Log.d(TAG, "开始GPS定位"); + } + + // 同时使用网络定位作为备选 + if (locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) { + locationManager.requestLocationUpdates( + LocationManager.NETWORK_PROVIDER, + 0, + 0, + locationListener + ); + Log.d(TAG, "开始网络定位"); + } + + // 尝试获取最后已知位置 + Location lastKnownLocation = getLastKnownLocation(); + if (lastKnownLocation != null) { + double lat = lastKnownLocation.getLatitude(); + double lon = lastKnownLocation.getLongitude(); + Log.d(TAG, "使用最后已知位置 - 纬度(Latitude): " + lat + ", 经度(Longitude): " + lon); + stopLocation(); + reverseGeocode(lon, lat); + } + + } catch (SecurityException e) { + Log.e(TAG, "定位权限异常", e); + if (resultListener != null) { + resultListener.onError("定位权限异常: " + e.getMessage()); + } + } + } + + /** + * 停止定位 + */ + public void stopLocation() { + if (locationManager != null && locationListener != null) { + try { + locationManager.removeUpdates(locationListener); + Log.d(TAG, "停止定位"); + } catch (SecurityException e) { + Log.e(TAG, "停止定位异常", e); + } + } + } + + /** + * 检查定位权限 + */ + private boolean checkLocationPermission() { + return ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) + == PackageManager.PERMISSION_GRANTED + || ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) + == PackageManager.PERMISSION_GRANTED; + } + + /** + * 检查定位服务是否开启 + */ + private boolean isLocationEnabled() { + return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) + || locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER); + } + + /** + * 获取最后已知位置 + */ + private Location getLastKnownLocation() { + try { + Location gpsLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER); + Location networkLocation = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER); + + if (gpsLocation != null && networkLocation != null) { + // 返回较新的位置 + return gpsLocation.getTime() > networkLocation.getTime() ? gpsLocation : networkLocation; + } else if (gpsLocation != null) { + return gpsLocation; + } else { + return networkLocation; + } + } catch (SecurityException e) { + Log.e(TAG, "获取最后已知位置异常", e); + return null; + } + } + + /** + * 逆地理编码 - 将经纬度转换为地址 + */ + private void reverseGeocode(final double longitude, final double latitude) { + // 保存经纬度 + this.currentLongitude = longitude; + this.currentLatitude = latitude; + + new Thread(() -> { + try { + Log.d(TAG, "开始逆地理编码 - 经度(Lon): " + longitude + ", 纬度(Lat): " + latitude); + + // 检查坐标是否在中国境内(粗略判断) + // 中国经度范围:73°E - 135°E,纬度范围:3°N - 53°N + if (longitude < 73 || longitude > 135 || latitude < 3 || latitude > 53) { + Log.w(TAG, "坐标不在中国境内 - 经度: " + longitude + " (应在73-135之间), 纬度: " + latitude + " (应在3-53之间)"); + if (resultListener != null) { + resultListener.onError("当前位置不在中国境内,天地图API仅支持中国地区\n坐标: " + latitude + ", " + longitude); + } + return; + } + + Log.d(TAG, "坐标检查通过,在中国境内"); + + // 构建请求URL + // 天地图逆地理编码API: http://api.tianditu.gov.cn/geocoder?postStr={'lon':116.397128,'lat':39.916527,'ver':1}&type=geocode&tk=您的密钥 + JSONObject postData = new JSONObject(); + postData.put("lon", longitude); + postData.put("lat", latitude); + postData.put("ver", 1); + + String urlString = GEOCODE_API_URL + "?postStr=" + URLEncoder.encode(postData.toString(), "UTF-8") + + "&type=geocode&tk=" + TIANDITU_API_KEY; + + Log.d(TAG, "逆地理编码请求: " + urlString); + + URL url = new URL(urlString); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); + connection.setConnectTimeout(10000); + connection.setReadTimeout(10000); + + int responseCode = connection.getResponseCode(); + Log.d(TAG, "响应码: " + responseCode); + + if (responseCode == HttpURLConnection.HTTP_OK) { + BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8")); + StringBuilder response = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + response.append(line); + } + reader.close(); + + String responseStr = response.toString(); + Log.d(TAG, "逆地理编码响应: " + responseStr); + + // 解析响应 + parseGeocodeResponse(responseStr); + } else { + Log.e(TAG, "逆地理编码失败,响应码: " + responseCode); + if (resultListener != null) { + resultListener.onError("获取地址失败,错误码: " + responseCode); + } + } + + connection.disconnect(); + + } catch (Exception e) { + Log.e(TAG, "逆地理编码异常", e); + if (resultListener != null) { + resultListener.onError("获取地址异常: " + e.getMessage()); + } + } + }).start(); + } + + /** + * 解析天地图逆地理编码响应 + */ + private void parseGeocodeResponse(String responseStr) { + try { + JSONObject json = new JSONObject(responseStr); + + // 检查状态 + String status = json.optString("status"); + Log.d(TAG, "API状态码: " + status); + + if (!"0".equals(status)) { + String msg = json.optString("msg", "未知错误"); + Log.e(TAG, "天地图API返回错误: status=" + status + ", msg=" + msg); + if (resultListener != null) { + resultListener.onError("获取地址失败: " + msg + " (状态码: " + status + ")"); + } + return; + } + + // 获取地址信息 + JSONObject result = json.optJSONObject("result"); + if (result != null) { + JSONObject addressComponent = result.optJSONObject("addressComponent"); + String formattedAddress = result.optString("formatted_address", ""); + + Log.d(TAG, "完整地址: " + formattedAddress); + + String province = ""; + String city = ""; + + if (addressComponent != null) { + province = addressComponent.optString("province", ""); + city = addressComponent.optString("city", ""); + String county = addressComponent.optString("county", ""); + + Log.d(TAG, "省份: " + province + ", 城市: " + city + ", 区县: " + county); + + // 如果city为空,尝试使用county + if (city.isEmpty()) { + city = county; + } + } + + // 如果省份和城市都为空,尝试使用完整地址 + if (province.isEmpty() && city.isEmpty() && !formattedAddress.isEmpty()) { + Log.d(TAG, "省份和城市为空,使用完整地址"); + province = formattedAddress; + } + + Log.d(TAG, "解析地址成功 - 省份: " + province + ", 城市: " + city + ", 完整地址: " + formattedAddress); + + if (resultListener != null) { + resultListener.onSuccess(province, city, formattedAddress, currentLatitude, currentLongitude); + } + } else { + Log.e(TAG, "响应中没有result字段"); + if (resultListener != null) { + resultListener.onError("地址解析失败:响应数据格式错误"); + } + } + + } catch (Exception e) { + Log.e(TAG, "解析响应异常", e); + if (resultListener != null) { + resultListener.onError("地址解析异常: " + e.getMessage()); + } + } + } +} diff --git a/android-app/app/src/main/java/com/example/livestreaming/net/ApiService.java b/android-app/app/src/main/java/com/example/livestreaming/net/ApiService.java index 31457264..222c7253 100644 --- a/android-app/app/src/main/java/com/example/livestreaming/net/ApiService.java +++ b/android-app/app/src/main/java/com/example/livestreaming/net/ApiService.java @@ -43,6 +43,17 @@ public interface ApiService { @POST("api/front/user/edit") Call> updateUserInfo(@Body UserEditRequest body); + @Multipart + @POST("api/front/user/edit/multipart") + Call> updateUserInfoWithFile( + @Part MultipartBody.Part avatarFile, + @Part("nickname") RequestBody nickname, + @Part("mark") RequestBody mark, + @Part("birthday") RequestBody birthday, + @Part("sex") RequestBody sex, + @Part("addres") RequestBody addres + ); + // ==================== 直播间 ==================== @GET("api/front/live/public/rooms") @@ -633,6 +644,13 @@ public interface ApiService { @Query("radius") int radius, @Query("limit") int limit); + /** + * 获取附近用户(简化版 - 只返回头像、昵称、地址) + */ + @GET("api/front/community/nearby-users-simple") + Call> getNearbyUsersSimple( + @Query("limit") int limit); + /** * 获取可匹配用户总数 */ @@ -784,4 +802,94 @@ public interface ApiService { */ @GET("api/front/fan-group/all") Call>>> getAllMyFanGroups(); + + // ==================== 消费记录接口 ==================== + + /** + * 获取消费记录 + */ + @GET("api/front/virtual-currency/consume-records") + Call>>> getConsumeRecords( + @Query("page") int page, + @Query("limit") int limit); + + // ==================== 黑名单接口 ==================== + + /** + * 获取我的黑名单列表 + */ + @GET("api/front/blacklist/my") + Call>>> getMyBlacklist( + @Query("page") int page, + @Query("limit") int limit); + + /** + * 从黑名单中移除 + */ + @POST("api/front/blacklist/remove") + Call>> removeFromBlacklist(@Body Map body); + + // ==================== 直播类型接口 ==================== + + /** + * 获取直播类型列表 + */ + @GET("api/front/live/types") + Call>> getLiveTypes(); + + // ==================== 调试接口 ==================== + + /** + * 调试 Token + */ + @GET("api/front/debug/token") + Call>> debugToken(); + + // ==================== 用户活动记录接口 ==================== + + /** + * 获取观看历史 + */ + @GET("api/front/user-activity/view-history") + Call>>> getViewHistory( + @Query("type") String type, + @Query("page") int page, + @Query("limit") int limit); + + /** + * 获取点赞记录 + */ + @GET("api/front/user-activity/like-records") + Call>>> getLikeRecords( + @Query("type") String type, + @Query("page") int page, + @Query("limit") int limit); + + /** + * 获取收藏的作品 + */ + @GET("api/front/user-activity/collected-works") + Call>>> getCollectedWorks( + @Query("page") int page, + @Query("limit") int limit); + + /** + * 获取关注记录 + */ + @GET("api/front/user-activity/follow-records") + Call>>> getFollowRecords( + @Query("page") int page, + @Query("limit") int limit); + + /** + * 记录观看历史(新版) + */ + @POST("api/front/user-activity/view-history") + Call>> recordViewHistoryNew(@Body Map body); + + /** + * 清除观看历史 + */ + @DELETE("api/front/user-activity/view-history") + Call> clearViewHistory(@Query("type") String type); } diff --git a/android-app/app/src/main/java/com/example/livestreaming/net/CommunityResponse.java b/android-app/app/src/main/java/com/example/livestreaming/net/CommunityResponse.java index 7da61855..892393a2 100644 --- a/android-app/app/src/main/java/com/example/livestreaming/net/CommunityResponse.java +++ b/android-app/app/src/main/java/com/example/livestreaming/net/CommunityResponse.java @@ -123,6 +123,54 @@ public class CommunityResponse { } } + /** + * 附近用户(简化版) + */ + public static class NearbyUserSimple { + public int id; + public String nickname; + public String avatar; + public String address; + public Double distance; // 距离(公里) + public String distanceText; // 距离文本(例如:1.2km) + + public int getUserId() { + return id; + } + + public String getNickname() { + return nickname; + } + + public String getAvatar() { + return avatar; + } + + public String getAddress() { + return address; + } + + public Double getDistance() { + return distance; + } + + public String getDistanceText() { + return distanceText; + } + } + + /** + * 附近用户简化列表 + */ + public static class NearbyUserSimpleList { + public List list; + public int total; + + public List getUsers() { + return list; + } + } + /** * 匹配用户(心动信号) */ diff --git a/android-app/app/src/main/java/com/example/livestreaming/net/UserEditRequest.java b/android-app/app/src/main/java/com/example/livestreaming/net/UserEditRequest.java index 25166c27..0001aebb 100644 --- a/android-app/app/src/main/java/com/example/livestreaming/net/UserEditRequest.java +++ b/android-app/app/src/main/java/com/example/livestreaming/net/UserEditRequest.java @@ -19,6 +19,12 @@ public class UserEditRequest { @SerializedName("addres") private String addres; // 地址 + @SerializedName("latitude") + private Double latitude; // 纬度 + + @SerializedName("longitude") + private Double longitude; // 经度 + @SerializedName("mark") private String mark; // 个人签名/备注 @@ -34,6 +40,8 @@ public class UserEditRequest { public void setBirthday(String birthday) { this.birthday = birthday; } public void setSex(Integer sex) { this.sex = sex; } public void setAddres(String addres) { this.addres = addres; } + public void setLatitude(Double latitude) { this.latitude = latitude; } + public void setLongitude(Double longitude) { this.longitude = longitude; } public void setMark(String mark) { this.mark = mark; } public String getNickname() { return nickname; } @@ -41,5 +49,7 @@ public class UserEditRequest { public String getBirthday() { return birthday; } public Integer getSex() { return sex; } public String getAddres() { return addres; } + public Double getLatitude() { return latitude; } + public Double getLongitude() { return longitude; } public String getMark() { return mark; } } diff --git a/android-app/app/src/main/res/layout/activity_edit_profile.xml b/android-app/app/src/main/res/layout/activity_edit_profile.xml index 33a45faf..fa9693f7 100644 --- a/android-app/app/src/main/res/layout/activity_edit_profile.xml +++ b/android-app/app/src/main/res/layout/activity_edit_profile.xml @@ -240,34 +240,6 @@ - - - - - - + app:layout_constraintTop_toBottomOf="@id/genderLayout" /> diff --git a/android-app/app/src/main/res/layout/layout_nearby_tab.xml b/android-app/app/src/main/res/layout/layout_nearby_tab.xml index c4689518..6ef18a0f 100644 --- a/android-app/app/src/main/res/layout/layout_nearby_tab.xml +++ b/android-app/app/src/main/res/layout/layout_nearby_tab.xml @@ -59,7 +59,8 @@ android:orientation="horizontal" android:gravity="center_vertical" android:paddingHorizontal="16dp" - android:paddingVertical="8dp"> + android:paddingVertical="8dp" + android:visibility="gone">