附近地址的代码编写

This commit is contained in:
ShiQi 2026-01-05 17:11:35 +08:00
parent fd27958b9d
commit b5fdbf1319
36 changed files with 2428 additions and 795 deletions

View File

@ -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 }
})
}

View File

@ -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
}
}
},

View File

@ -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<List<CommunityCategory>> categoryList() {
return CommonResult.success(communityService.getCategoryList());
}
//@PreAuthorize("hasAuthority('admin:community:category')")
@ApiOperation("保存板块")
@PostMapping("/category/save")
public CommonResult<String> categorySave(@RequestBody CommunityCategory category) {
communityService.saveCategory(category);
return CommonResult.success("保存成功");
}
//@PreAuthorize("hasAuthority('admin:community:category')")
@ApiOperation("删除板块")
@DeleteMapping("/category/delete/{id}")
public CommonResult<String> categoryDelete(@PathVariable Integer id) {
communityService.deleteCategory(id);
return CommonResult.success("删除成功");
}
//@PreAuthorize("hasAuthority('admin:community:category')")
@ApiOperation("更新板块状态")
@PostMapping("/category/status")
public CommonResult<String> categoryStatus(@RequestBody CommunityCategory category) {
communityService.updateCategoryStatus(category.getId(), category.getStatus());
return CommonResult.success("更新成功");
}
// ==================== 消息管理 ====================
//@PreAuthorize("hasAuthority('admin:community:message')")
@ApiOperation("消息列表")
@PostMapping("/message/list")
public CommonResult<PageInfo<CommunityMessageVO>> messageList(@RequestBody CommunityMessageRequest request) {
return CommonResult.success(communityService.getMessageList(request));
}
//@PreAuthorize("hasAuthority('admin:community:message')")
@ApiOperation("审核消息")
@PostMapping("/message/audit")
public CommonResult<String> 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<String> messageDelete(@PathVariable Long id) {
communityService.deleteMessage(id);
return CommonResult.success("删除成功");
}
// ==================== 匹配配置 ====================
//@PreAuthorize("hasAuthority('admin:community:match')")
@ApiOperation("获取匹配配置")
@GetMapping("/match/config")
public CommonResult<CommunityMatchConfig> getMatchConfig() {
return CommonResult.success(communityService.getMatchConfig());
}
//@PreAuthorize("hasAuthority('admin:community:match')")
@ApiOperation("保存匹配配置")
@PostMapping("/match/config/save")
public CommonResult<String> saveMatchConfig(@RequestBody CommunityMatchConfig config) {
communityService.saveMatchConfig(config);
return CommonResult.success("保存成功");
}
}

View File

@ -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<CommonPage<Map<String, Object>>> 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<Object> params = new ArrayList<>();
List<Object> 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<Map<String, Object>> list = jdbcTemplate.queryForList(sql.toString(), params.toArray());
CommonPage<Map<String, Object>> 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<Map<String, Object>> getGiftStatistics() {
try {
Map<String, Object> 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<List<Map<String, Object>>> 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<Map<String, Object>> list = jdbcTemplate.queryForList(sql);
return CommonResult.success(list);
} catch (Exception e) {
log.error("获取礼物配置失败", e);
return CommonResult.failed("获取礼物配置失败");
}
}
/**
* 添加礼物配置
*/
@ApiOperation(value = "添加礼物配置")
@PostMapping("/config/add")
public CommonResult<String> addGiftConfig(@RequestBody Map<String, Object> 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<String> updateGiftConfig(@RequestBody Map<String, Object> 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<String> 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<List<Map<String, Object>>> 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<Map<String, Object>> 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<String> updateRechargePackage(@RequestBody Map<String, Object> 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<CommonPage<Map<String, Object>>> 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<Object> params = new ArrayList<>();
List<Object> 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<Map<String, Object>> list = jdbcTemplate.queryForList(sql.toString(), params.toArray());
CommonPage<Map<String, Object>> 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<CommonPage<Map<String, Object>>> 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<Object> params = new ArrayList<>();
List<Object> 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<Map<String, Object>> list = jdbcTemplate.queryForList(sql.toString(), params.toArray());
// 打印日志用于调试
log.info("礼物列表查询 - 总数: {}, 当前页: {}, 每页: {}, offset: {}, 列表大小: {}", total, page, limit, offset, list.size());
CommonPage<Map<String, Object>> 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<CommonPage<Gift>> getGiftList(GiftSearchRequest request) {
log.info("礼物列表查询 - 参数: {}", request);
CommonPage<Gift> 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<String> addGift(@RequestBody Map<String, Object> 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<String> 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<String> updateGift(@RequestBody Map<String, Object> 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<String> 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<String> deleteGift(@RequestBody Map<String, Object> request) {
try {
@SuppressWarnings("unchecked")
List<Integer> ids = (List<Integer>) 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<String> deleteGift(@RequestBody List<Integer> 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<String> updateGiftStatus(@RequestBody Map<String, Object> 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<String> 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<Map<String, Object>> getGiftStatistics() {
log.info("获取礼物记录统计数据");
Map<String, Object> statistics = giftRecordService.getGiftRecordStatistics();
return CommonResult.success(statistics);
}
/**
* 获取礼物打赏记录列表
*/
@ApiOperation(value = "礼物打赏记录列表")
@GetMapping("/records")
public CommonResult<CommonPage<Map<String, Object>>> getGiftRecords(GiftRecordSearchRequest request) {
log.info("获取礼物打赏记录 - 参数: {}", request);
CommonPage<Map<String, Object>> result = giftRecordService.getGiftRecordList(request);
log.info("获取礼物打赏记录 - 总数: {}, 当前页: {}, 列表大小: {}",
result.getTotal(), result.getPage(), result.getList().size());
return CommonResult.success(result);
}
}

View File

@ -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开始支持,详情看下面。

View File

@ -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;

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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"

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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

View File

@ -70,6 +70,26 @@ public class CommunityFrontController {
return CommonResult.success(result);
}
@ApiOperation("获取附近用户(简化版 - 只返回头像、昵称、地址)")
@GetMapping("/nearby-users-simple")
public CommonResult<java.util.Map<String, Object>> getNearbyUsersSimple(
@RequestParam(defaultValue = "100") Integer limit) {
log.info("=== 收到获取附近用户请求 - limit: {} ===", limit);
List<com.zbkj.common.vo.NearbyUserSimpleVO> users = communityService.getNearbyUsersSimple(limit);
java.util.Map<String, Object> 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<String> test() {
log.info("=== 测试接口被调用 ===");
return CommonResult.success("CommunityFrontController is working!");
}
@ApiOperation("获取可匹配用户总数")
@GetMapping("/user-count")
public CommonResult<Integer> getMatchableUserCount() {

View File

@ -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<Object> 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());
}
}
/**
* 个人中心-用户信息
*/

View File

@ -27,23 +27,9 @@
<!-- 控制台输出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>-->
<pattern>
<pattern>
{
"app": "${APP_NAME}",
"timestamp":"%d{yyyy-MM-dd HH:mm:ss.SSS}",
"level": "%level",
"thread": "%thread",
"class": "%logger{40}",
"message": "%msg" }
%n
</pattern>
</pattern>
<!-- 简化的日志格式,便于阅读 -->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>utf-8</charset>
</encoder>
</appender>

View File

@ -89,6 +89,11 @@ public interface CommunityService {
*/
List<NearbyUserVO> getNearbyUsers(Double latitude, Double longitude, Integer radius, Integer limit);
/**
* 获取附近用户简化版 - 只返回头像昵称地址
*/
List<com.zbkj.common.vo.NearbyUserSimpleVO> getNearbyUsersSimple(Integer limit);
/**
* 获取可匹配用户总数排除当前用户
*/

View File

@ -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<GiftRecord> {
* @return 总消费钻石数
*/
Long getTotalDiamondByUser(Integer roomId, Integer userId);
/**
* 分页查询礼物记录带用户信息
* @param request 搜索请求
* @return 分页结果
*/
CommonPage<Map<String, Object>> getGiftRecordList(GiftRecordSearchRequest request);
/**
* 获取礼物记录统计数据
* @return 统计数据
*/
Map<String, Object> getGiftRecordStatistics();
}

View File

@ -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<Gift> {
* 根据ID获取礼物
*/
Gift getGiftById(Integer giftId);
/**
* 分页查询礼物列表
*/
CommonPage<Gift> getGiftList(GiftSearchRequest request);
/**
* 更新礼物状态
*/
boolean updateStatus(Integer id, Integer status);
/**
* 批量删除礼物逻辑删除
*/
boolean deleteGifts(List<Integer> ids);
/**
* 获取礼物统计数据
*/
Map<String, Object> getGiftStatistics();
/**
* 获取礼物记录统计数据
*/
Map<String, Object> getGiftRecordStatistics();
}

View File

@ -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<com.zbkj.common.vo.NearbyUserSimpleVO> 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<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getStatus, true); // 只查询正常状态的用户
if (currentUserId != null) {
wrapper.ne(User::getUid, currentUserId); // 排除当前用户
}
wrapper.last("ORDER BY RAND() LIMIT " + limit); // 随机获取
List<User> users = userService.list(wrapper);
log.info("从数据库查询到用户数量: {}", users.size());
List<com.zbkj.common.vo.NearbyUserSimpleVO> 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);
}
}
}

View File

@ -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<GiftRecordDao, GiftRecord
public Long getTotalDiamondByUser(Integer roomId, Integer userId) {
return giftRecordDao.getTotalDiamondByUser(roomId, userId);
}
@Override
public CommonPage<Map<String, Object>> getGiftRecordList(GiftRecordSearchRequest request) {
Page<GiftRecord> pageParam = new Page<>(request.getPage(), request.getLimit());
LambdaQueryWrapper<GiftRecord> 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<GiftRecord> pageResult = page(pageParam, wrapper);
// 转换为 Map 格式包含用户信息
List<Map<String, Object>> resultList = pageResult.getRecords().stream().map(record -> {
Map<String, Object> 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<String, Object> getGiftRecordStatistics() {
Map<String, Object> statistics = new HashMap<>();
// 总礼物记录数
long totalCount = count();
statistics.put("totalCount", totalCount);
// 总价值
List<GiftRecord> 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<GiftRecord> 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<GiftRecord> 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;
}
}

View File

@ -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<GiftDao, Gift> implements GiftS
public List<Gift> getActiveGifts() {
LambdaQueryWrapper<Gift> 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<GiftDao, Gift> implements GiftS
public Gift getGiftById(Integer giftId) {
return getById(giftId);
}
@Override
public CommonPage<Gift> getGiftList(GiftSearchRequest request) {
Page<Gift> pageParam = new Page<>(request.getPage(), request.getLimit());
LambdaQueryWrapper<Gift> 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<Gift> 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<Integer> ids) {
return removeByIds(ids);
}
@Override
public Map<String, Object> getGiftStatistics() {
Map<String, Object> statistics = new java.util.HashMap<>();
// 礼物总数
LambdaQueryWrapper<Gift> totalWrapper = new LambdaQueryWrapper<>();
long total = count(totalWrapper);
statistics.put("total", total);
// 启用的礼物数
LambdaQueryWrapper<Gift> enabledWrapper = new LambdaQueryWrapper<>();
enabledWrapper.eq(Gift::getStatus, 1);
long enabled = count(enabledWrapper);
statistics.put("enabled", enabled);
// 禁用的礼物数
LambdaQueryWrapper<Gift> disabledWrapper = new LambdaQueryWrapper<>();
disabledWrapper.eq(Gift::getStatus, 0);
long disabled = count(disabledWrapper);
statistics.put("disabled", disabled);
// 总价值所有礼物的钻石价格总和
List<Gift> 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<String, Object> getGiftRecordStatistics() {
// 这个方法应该由 GiftRecordService 实现
// 这里只是占位实际调用会转发到 GiftRecordService
return new java.util.HashMap<>();
}
}

View File

@ -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);

View File

@ -1747,38 +1747,115 @@ public class UserServiceImpl extends ServiceImpl<UserDao, User> 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());
}
}
/**

View File

@ -12,6 +12,9 @@
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<!-- 位置权限,用于附近功能 -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- Android 10+ 需要此权限来显示来电全屏界面 -->
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
<!-- 前台服务权限,用于保持通话连接 -->

View File

@ -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<String> pickImageLauncher;
private ActivityResultLauncher<Uri> takePictureLauncher;
private ActivityResultLauncher<String> requestCameraPermissionLauncher;
private ActivityResultLauncher<String> requestStoragePermissionLauncher;
private ActivityResultLauncher<String[]> 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<ApiResponse<Object>>() {
@Override
public void onResponse(Call<ApiResponse<Object>> call, Response<ApiResponse<Object>> 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<ApiResponse<Object>> 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<ApiResponse<FileUploadResponse>>() {
@Override
public void onResponse(Call<ApiResponse<FileUploadResponse>> call, Response<ApiResponse<FileUploadResponse>> 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<ApiResponse<FileUploadResponse>> 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);
}
}

View File

@ -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<Room> discoverRooms = new ArrayList<>(); // 发现页面的房间列表
private final List<NearbyUser> 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<ApiResponse<CommunityResponse.NearbyUserList>>() {
// 从后端获取附近用户使用简化接口
Log.d(TAG, "开始加载附近用户,调用接口: /api/front/community/nearby-users-simple");
ApiClient.getService(getApplicationContext()).getNearbyUsersSimple(100)
.enqueue(new Callback<ApiResponse<CommunityResponse.NearbyUserSimpleList>>() {
@Override
public void onResponse(Call<ApiResponse<CommunityResponse.NearbyUserList>> call,
Response<ApiResponse<CommunityResponse.NearbyUserList>> response) {
public void onResponse(Call<ApiResponse<CommunityResponse.NearbyUserSimpleList>> call,
Response<ApiResponse<CommunityResponse.NearbyUserSimpleList>> 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<CommunityResponse.NearbyUser> 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<CommunityResponse.NearbyUserSimple> 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<ApiResponse<CommunityResponse.NearbyUserList>> call, Throwable t) {
public void onFailure(Call<ApiResponse<CommunityResponse.NearbyUserSimpleList>> 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<ApiResponse<CommunityResponse.NearbyUserSimpleList>>() {
@Override
public void onResponse(Call<ApiResponse<CommunityResponse.NearbyUserSimpleList>> call,
Response<ApiResponse<CommunityResponse.NearbyUserSimpleList>> 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<CommunityResponse.NearbyUserSimple> shuffledUsers = new ArrayList<>(userList.getUsers());
Collections.shuffle(shuffledUsers);
// 获取当前已有的用户ID避免重复
Set<String> 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<ApiResponse<CommunityResponse.NearbyUserSimpleList>> 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();
}
}
}
}

View File

@ -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<String[]> 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<ApiResponse<Object>>() {
@Override
public void onResponse(Call<ApiResponse<Object>> call, Response<ApiResponse<Object>> response) {
Log.d(TAG, "========== 收到服务器响应 ==========");
Log.d(TAG, "HTTP状态码: " + response.code());
if (response.isSuccessful() && response.body() != null) {
ApiResponse<Object> 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<ApiResponse<Object>> 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<ApiResponse<Object>>() {
@Override
public void onResponse(Call<ApiResponse<Object>> call, Response<ApiResponse<Object>> 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<ApiResponse<Object>> call, Throwable t) {
Log.w(TAG, "位置更新网络错误: " + t.getMessage());
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
// 停止定位服务
if (locationService != null) {
locationService.stopLocation();
}
}
}

View File

@ -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)

View File

@ -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());
}
}
}
}

View File

@ -43,6 +43,17 @@ public interface ApiService {
@POST("api/front/user/edit")
Call<ApiResponse<Object>> updateUserInfo(@Body UserEditRequest body);
@Multipart
@POST("api/front/user/edit/multipart")
Call<ApiResponse<Object>> 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<ApiResponse<CommunityResponse.NearbyUserSimpleList>> getNearbyUsersSimple(
@Query("limit") int limit);
/**
* 获取可匹配用户总数
*/
@ -784,4 +802,94 @@ public interface ApiService {
*/
@GET("api/front/fan-group/all")
Call<ApiResponse<List<Map<String, Object>>>> getAllMyFanGroups();
// ==================== 消费记录接口 ====================
/**
* 获取消费记录
*/
@GET("api/front/virtual-currency/consume-records")
Call<ApiResponse<List<Map<String, Object>>>> getConsumeRecords(
@Query("page") int page,
@Query("limit") int limit);
// ==================== 黑名单接口 ====================
/**
* 获取我的黑名单列表
*/
@GET("api/front/blacklist/my")
Call<ApiResponse<PageResponse<Map<String, Object>>>> getMyBlacklist(
@Query("page") int page,
@Query("limit") int limit);
/**
* 从黑名单中移除
*/
@POST("api/front/blacklist/remove")
Call<ApiResponse<Map<String, Object>>> removeFromBlacklist(@Body Map<String, Object> body);
// ==================== 直播类型接口 ====================
/**
* 获取直播类型列表
*/
@GET("api/front/live/types")
Call<ApiResponse<List<LiveTypeResponse>>> getLiveTypes();
// ==================== 调试接口 ====================
/**
* 调试 Token
*/
@GET("api/front/debug/token")
Call<ApiResponse<Map<String, Object>>> debugToken();
// ==================== 用户活动记录接口 ====================
/**
* 获取观看历史
*/
@GET("api/front/user-activity/view-history")
Call<ApiResponse<PageResponse<Map<String, Object>>>> getViewHistory(
@Query("type") String type,
@Query("page") int page,
@Query("limit") int limit);
/**
* 获取点赞记录
*/
@GET("api/front/user-activity/like-records")
Call<ApiResponse<PageResponse<Map<String, Object>>>> getLikeRecords(
@Query("type") String type,
@Query("page") int page,
@Query("limit") int limit);
/**
* 获取收藏的作品
*/
@GET("api/front/user-activity/collected-works")
Call<ApiResponse<PageResponse<Map<String, Object>>>> getCollectedWorks(
@Query("page") int page,
@Query("limit") int limit);
/**
* 获取关注记录
*/
@GET("api/front/user-activity/follow-records")
Call<ApiResponse<PageResponse<Map<String, Object>>>> getFollowRecords(
@Query("page") int page,
@Query("limit") int limit);
/**
* 记录观看历史新版
*/
@POST("api/front/user-activity/view-history")
Call<ApiResponse<Map<String, Object>>> recordViewHistoryNew(@Body Map<String, Object> body);
/**
* 清除观看历史
*/
@DELETE("api/front/user-activity/view-history")
Call<ApiResponse<String>> clearViewHistory(@Query("type") String type);
}

View File

@ -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<NearbyUserSimple> list;
public int total;
public List<NearbyUserSimple> getUsers() {
return list;
}
}
/**
* 匹配用户心动信号
*/

View File

@ -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; }
}

View File

@ -240,34 +240,6 @@
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/locationLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:hint="所在地"
app:boxBackgroundMode="filled"
app:boxBackgroundColor="@android:color/white"
app:boxCornerRadiusTopStart="14dp"
app:boxCornerRadiusTopEnd="14dp"
app:boxCornerRadiusBottomStart="14dp"
app:boxCornerRadiusBottomEnd="14dp"
app:boxStrokeColor="#22000000"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/genderLayout">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/inputLocation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="1"
android:focusable="false"
android:clickable="true"
android:cursorVisible="false" />
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:id="@+id/cancelButton"
android:layout_width="0dp"
@ -279,7 +251,7 @@
android:textColor="#111111"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/locationLayout" />
app:layout_constraintTop_toBottomOf="@id/genderLayout" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -59,7 +59,8 @@
android:orientation="horizontal"
android:gravity="center_vertical"
android:paddingHorizontal="16dp"
android:paddingVertical="8dp">
android:paddingVertical="8dp"
android:visibility="gone">
<ImageView
android:layout_width="20dp"