附近地址的代码编写
This commit is contained in:
parent
fd27958b9d
commit
b5fdbf1319
|
|
@ -111,7 +111,7 @@ export function giftDeleteApi(ids) {
|
||||||
return request({
|
return request({
|
||||||
url: '/admin/gift/delete',
|
url: '/admin/gift/delete',
|
||||||
method: 'post',
|
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({
|
return request({
|
||||||
url: '/admin/gift/status',
|
url: '/admin/gift/status',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
data: { id, status }
|
params: { id, status }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,18 @@ module.exports = {
|
||||||
'/file': {
|
'/file': {
|
||||||
target: 'http://localhost:30001',
|
target: 'http://localhost:30001',
|
||||||
changeOrigin: true
|
changeOrigin: true
|
||||||
|
},
|
||||||
|
'/image': {
|
||||||
|
target: 'http://localhost:30001',
|
||||||
|
changeOrigin: true
|
||||||
|
},
|
||||||
|
'/video': {
|
||||||
|
target: 'http://localhost:30001',
|
||||||
|
changeOrigin: true
|
||||||
|
},
|
||||||
|
'/voice': {
|
||||||
|
target: 'http://localhost:30001',
|
||||||
|
changeOrigin: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -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("保存成功");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +1,28 @@
|
||||||
package com.zbkj.admin.controller;
|
package com.zbkj.admin.controller;
|
||||||
|
|
||||||
|
import com.zbkj.common.model.gift.Gift;
|
||||||
import com.zbkj.common.page.CommonPage;
|
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.common.result.CommonResult;
|
||||||
|
import com.zbkj.service.service.GiftRecordService;
|
||||||
|
import com.zbkj.service.service.GiftService;
|
||||||
import io.swagger.annotations.Api;
|
import io.swagger.annotations.Api;
|
||||||
import io.swagger.annotations.ApiOperation;
|
import io.swagger.annotations.ApiOperation;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.BeanUtils;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import javax.validation.Valid;
|
||||||
import java.util.*;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 礼物管理控制器(后台管理)
|
* 礼物管理控制器(后台管理)- MVC架构
|
||||||
|
* Controller层:负责接收请求、参数校验、调用Service层、返回响应
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@RestController
|
@RestController
|
||||||
|
|
@ -24,386 +32,21 @@ import java.util.*;
|
||||||
public class GiftAdminController {
|
public class GiftAdminController {
|
||||||
|
|
||||||
@Autowired
|
@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 = "礼物列表")
|
@ApiOperation(value = "礼物列表")
|
||||||
@GetMapping("/list")
|
@GetMapping("/list")
|
||||||
public CommonResult<CommonPage<Map<String, Object>>> getGiftList(
|
public CommonResult<CommonPage<Gift>> getGiftList(GiftSearchRequest request) {
|
||||||
@RequestParam(value = "name", required = false) String name,
|
log.info("礼物列表查询 - 参数: {}", request);
|
||||||
@RequestParam(value = "status", required = false) Integer status,
|
CommonPage<Gift> result = giftService.getGiftList(request);
|
||||||
@RequestParam(value = "page", defaultValue = "1") Integer page,
|
log.info("礼物列表查询 - 总数: {}, 当前页: {}, 列表大小: {}",
|
||||||
@RequestParam(value = "limit", defaultValue = "20") Integer limit) {
|
result.getTotal(), result.getPage(), result.getList().size());
|
||||||
|
|
||||||
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));
|
|
||||||
|
|
||||||
return CommonResult.success(result);
|
return CommonResult.success(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -412,35 +55,19 @@ public class GiftAdminController {
|
||||||
*/
|
*/
|
||||||
@ApiOperation(value = "添加礼物")
|
@ApiOperation(value = "添加礼物")
|
||||||
@PostMapping("/add")
|
@PostMapping("/add")
|
||||||
public CommonResult<String> addGift(@RequestBody Map<String, Object> request) {
|
public CommonResult<String> addGift(@Valid @RequestBody GiftRequest request) {
|
||||||
try {
|
log.info("添加礼物 - 请求参数: {}", request);
|
||||||
log.info("添加礼物 - 请求参数: {}", request);
|
|
||||||
|
Gift gift = new Gift();
|
||||||
String name = (String) request.get("name");
|
BeanUtils.copyProperties(request, gift);
|
||||||
String image = (String) request.get("image");
|
|
||||||
BigDecimal diamondPrice = new BigDecimal(request.get("diamondPrice").toString());
|
boolean success = giftService.save(gift);
|
||||||
Integer intimacy = request.get("intimacy") != null ? Integer.parseInt(request.get("intimacy").toString()) : 0;
|
if (success) {
|
||||||
Integer level = request.get("level") != null ? Integer.parseInt(request.get("level").toString()) : 1;
|
log.info("添加礼物成功 - 名称: {}", request.getName());
|
||||||
|
|
||||||
// 处理布尔值或整数值
|
|
||||||
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);
|
|
||||||
return CommonResult.success("添加成功");
|
return CommonResult.success("添加成功");
|
||||||
} catch (Exception e) {
|
} else {
|
||||||
log.error("添加礼物失败", e);
|
log.error("添加礼物失败 - 名称: {}", request.getName());
|
||||||
return CommonResult.failed("添加失败: " + e.getMessage());
|
return CommonResult.failed("添加失败");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -449,89 +76,49 @@ public class GiftAdminController {
|
||||||
*/
|
*/
|
||||||
@ApiOperation(value = "更新礼物")
|
@ApiOperation(value = "更新礼物")
|
||||||
@PostMapping("/update")
|
@PostMapping("/update")
|
||||||
public CommonResult<String> updateGift(@RequestBody Map<String, Object> request) {
|
public CommonResult<String> updateGift(@Valid @RequestBody GiftRequest request) {
|
||||||
try {
|
log.info("更新礼物 - 请求参数: {}", request);
|
||||||
log.info("更新礼物 - 请求参数: {}", request);
|
|
||||||
|
if (request.getId() == null) {
|
||||||
Integer id = Integer.parseInt(request.get("id").toString());
|
return CommonResult.failed("礼物ID不能为空");
|
||||||
String name = (String) request.get("name");
|
}
|
||||||
String image = (String) request.get("image");
|
|
||||||
BigDecimal diamondPrice = new BigDecimal(request.get("diamondPrice").toString());
|
Gift gift = giftService.getById(request.getId());
|
||||||
Integer intimacy = request.get("intimacy") != null ? Integer.parseInt(request.get("intimacy").toString()) : 0;
|
if (gift == null) {
|
||||||
Integer level = request.get("level") != null ? Integer.parseInt(request.get("level").toString()) : 1;
|
return CommonResult.failed("礼物不存在");
|
||||||
|
}
|
||||||
// 处理布尔值或整数值
|
|
||||||
Integer isHeartbeat = convertToInteger(request.get("isHeartbeat"), 0);
|
BeanUtils.copyProperties(request, gift);
|
||||||
Integer sort = request.get("sort") != null ? Integer.parseInt(request.get("sort").toString()) : 0;
|
|
||||||
Integer status = convertToInteger(request.get("status"), 1);
|
boolean success = giftService.updateById(gift);
|
||||||
|
if (success) {
|
||||||
String remark = (String) request.get("remark");
|
log.info("更新礼物成功 - ID: {}", request.getId());
|
||||||
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);
|
|
||||||
return CommonResult.success("更新成功");
|
return CommonResult.success("更新成功");
|
||||||
} catch (Exception e) {
|
} else {
|
||||||
log.error("更新礼物失败", e);
|
log.error("更新礼物失败 - ID: {}", request.getId());
|
||||||
return CommonResult.failed("更新失败: " + e.getMessage());
|
return CommonResult.failed("更新失败");
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将对象转换为整数(支持布尔值、字符串、整数)
|
|
||||||
*/
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除礼物
|
* 删除礼物(支持批量删除)
|
||||||
*/
|
*/
|
||||||
@ApiOperation(value = "删除礼物")
|
@ApiOperation(value = "删除礼物")
|
||||||
@PostMapping("/delete")
|
@PostMapping("/delete")
|
||||||
public CommonResult<String> deleteGift(@RequestBody Map<String, Object> request) {
|
public CommonResult<String> deleteGift(@RequestBody List<Integer> ids) {
|
||||||
try {
|
log.info("删除礼物 - IDs: {}", ids);
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
List<Integer> ids = (List<Integer>) request.get("ids");
|
if (ids == null || ids.isEmpty()) {
|
||||||
if (ids == null || ids.isEmpty()) {
|
return CommonResult.failed("请选择要删除的礼物");
|
||||||
return CommonResult.failed("请选择要删除的礼物");
|
}
|
||||||
}
|
|
||||||
|
boolean success = giftService.deleteGifts(ids);
|
||||||
String placeholders = String.join(",", Collections.nCopies(ids.size(), "?"));
|
if (success) {
|
||||||
String sql = "UPDATE eb_gift SET is_deleted = 1 WHERE id IN (" + placeholders + ")";
|
log.info("删除礼物成功 - 数量: {}", ids.size());
|
||||||
jdbcTemplate.update(sql, ids.toArray());
|
|
||||||
|
|
||||||
return CommonResult.success("删除成功");
|
return CommonResult.success("删除成功");
|
||||||
} catch (Exception e) {
|
} else {
|
||||||
log.error("删除礼物失败", e);
|
log.error("删除礼物失败");
|
||||||
return CommonResult.failed("删除失败: " + e.getMessage());
|
return CommonResult.failed("删除失败");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -540,21 +127,40 @@ public class GiftAdminController {
|
||||||
*/
|
*/
|
||||||
@ApiOperation(value = "更新礼物状态")
|
@ApiOperation(value = "更新礼物状态")
|
||||||
@PostMapping("/status")
|
@PostMapping("/status")
|
||||||
public CommonResult<String> updateGiftStatus(@RequestBody Map<String, Object> request) {
|
public CommonResult<String> updateStatus(@RequestParam Integer id, @RequestParam Integer status) {
|
||||||
try {
|
log.info("更新礼物状态 - ID: {}, 状态: {}", id, status);
|
||||||
log.info("更新礼物状态 - 请求参数: {}", request);
|
|
||||||
|
boolean success = giftService.updateStatus(id, status);
|
||||||
Integer id = Integer.parseInt(request.get("id").toString());
|
if (success) {
|
||||||
Integer status = convertToInteger(request.get("status"), 1);
|
log.info("更新礼物状态成功 - ID: {}", id);
|
||||||
|
return CommonResult.success("更新成功");
|
||||||
String sql = "UPDATE eb_gift SET status = ? WHERE id = ?";
|
} else {
|
||||||
jdbcTemplate.update(sql, status, id);
|
log.error("更新礼物状态失败 - ID: {}", id);
|
||||||
|
return CommonResult.failed("更新失败");
|
||||||
log.info("更新礼物状态成功 - ID: {}, 状态: {}", id, status);
|
|
||||||
return CommonResult.success("状态更新成功");
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("更新礼物状态失败", e);
|
|
||||||
return CommonResult.failed("状态更新失败: " + e.getMessage());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取礼物统计数据(礼物记录统计)
|
||||||
|
*/
|
||||||
|
@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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -136,6 +136,7 @@ mybatis-plus:
|
||||||
# 配置slq打印日志
|
# 配置slq打印日志
|
||||||
configuration:
|
configuration:
|
||||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||||
|
map-underscore-to-camel-case: true # 开启驼峰命名转换:数据库字段 diamond_price -> Java属性 diamondPrice
|
||||||
global-config:
|
global-config:
|
||||||
db-config:
|
db-config:
|
||||||
# logic-delete-field: isDel #全局逻辑删除字段值 3.3.0开始支持,详情看下面。
|
# logic-delete-field: isDel #全局逻辑删除字段值 3.3.0开始支持,详情看下面。
|
||||||
|
|
|
||||||
|
|
@ -133,6 +133,15 @@ public class User implements Serializable {
|
||||||
@ApiModelProperty(value = "详细地址")
|
@ApiModelProperty(value = "详细地址")
|
||||||
private String addres;
|
private String addres;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "纬度")
|
||||||
|
private BigDecimal latitude;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "经度")
|
||||||
|
private BigDecimal longitude;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "位置更新时间")
|
||||||
|
private Date locationUpdateTime;
|
||||||
|
|
||||||
@ApiModelProperty(value = "管理员编号 ")
|
@ApiModelProperty(value = "管理员编号 ")
|
||||||
private Integer adminid;
|
private Integer adminid;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -9,6 +9,7 @@ import org.hibernate.validator.constraints.Length;
|
||||||
|
|
||||||
import javax.validation.constraints.NotBlank;
|
import javax.validation.constraints.NotBlank;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户编辑Request
|
* 用户编辑Request
|
||||||
|
|
@ -31,7 +32,6 @@ public class UserEditRequest implements Serializable {
|
||||||
private static final long serialVersionUID=1L;
|
private static final long serialVersionUID=1L;
|
||||||
|
|
||||||
@ApiModelProperty(value = "用户昵称")
|
@ApiModelProperty(value = "用户昵称")
|
||||||
@NotBlank(message = "请填写用户昵称")
|
|
||||||
@Length(max = 255, message = "用户昵称不能超过255个字符")
|
@Length(max = 255, message = "用户昵称不能超过255个字符")
|
||||||
private String nickname;
|
private String nickname;
|
||||||
|
|
||||||
|
|
@ -50,6 +50,12 @@ public class UserEditRequest implements Serializable {
|
||||||
@Length(max = 255, message = "地址不能超过255个字符")
|
@Length(max = 255, message = "地址不能超过255个字符")
|
||||||
private String addres;
|
private String addres;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "纬度")
|
||||||
|
private BigDecimal latitude;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "经度")
|
||||||
|
private BigDecimal longitude;
|
||||||
|
|
||||||
@ApiModelProperty(value = "个人签名/备注")
|
@ApiModelProperty(value = "个人签名/备注")
|
||||||
@Length(max = 255, message = "个人签名不能超过255个字符")
|
@Length(max = 255, message = "个人签名不能超过255个字符")
|
||||||
private String mark;
|
private String mark;
|
||||||
|
|
|
||||||
|
|
@ -208,6 +208,7 @@ public class FrontTokenComponent {
|
||||||
"api/front/community/categories",
|
"api/front/community/categories",
|
||||||
"api/front/community/messages",
|
"api/front/community/messages",
|
||||||
"api/front/community/nearby-users",
|
"api/front/community/nearby-users",
|
||||||
|
"api/front/community/nearby-users-simple",
|
||||||
"api/front/community/user-count",
|
"api/front/community/user-count",
|
||||||
// 直播间公开接口
|
// 直播间公开接口
|
||||||
"api/front/live/public"
|
"api/front/live/public"
|
||||||
|
|
|
||||||
|
|
@ -83,28 +83,59 @@ public class UploadUtil {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据文件的绝对路径创建一个文件对象.
|
* 根据文件的绝对路径创建一个文件对象.
|
||||||
|
* 注意:只创建目录,不创建文件本身(文件由transferTo创建)
|
||||||
* @return 返回创建的这个文件对象
|
* @return 返回创建的这个文件对象
|
||||||
* @author Mr.Zhang
|
* @author Mr.Zhang
|
||||||
* @since 2020-05-08
|
* @since 2020-05-08
|
||||||
*/
|
*/
|
||||||
public static File createFile(String filePath) throws IOException {
|
public static File createFile(String filePath) throws IOException {
|
||||||
|
System.out.println("=== 创建文件 ===");
|
||||||
|
System.out.println("目标路径: " + filePath);
|
||||||
|
|
||||||
// 获取文件的完整目录
|
// 获取文件的完整目录
|
||||||
String fileDir = FilenameUtils.getFullPath(filePath);
|
String fileDir = FilenameUtils.getFullPath(filePath);
|
||||||
|
System.out.println("目录路径: " + fileDir);
|
||||||
|
|
||||||
// 判断目录是否存在,不存在就创建一个目录
|
// 判断目录是否存在,不存在就创建一个目录
|
||||||
File file = new File(fileDir);
|
File dir = new File(fileDir);
|
||||||
if (!file.isDirectory()) {
|
System.out.println("目录是否存在: " + dir.exists());
|
||||||
//创建失败返回null
|
System.out.println("是否是目录: " + dir.isDirectory());
|
||||||
if (!file.mkdirs()) {
|
|
||||||
throw new CrmebException("文件目录创建失败...");
|
if (!dir.exists()) {
|
||||||
}
|
System.out.println("目录不存在,开始创建...");
|
||||||
}
|
boolean created = dir.mkdirs();
|
||||||
// 判断这个文件是否存在,不存在就创建
|
System.out.println("目录创建结果: " + created);
|
||||||
file = new File(filePath);
|
|
||||||
if (!file.exists()) {
|
if (!created) {
|
||||||
if (!file.createNewFile()) {
|
// 检查父目录权限
|
||||||
throw new CrmebException("目标文件创建失败...");
|
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;
|
return 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;
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
package com.zbkj.front.config;
|
package com.zbkj.front.config;
|
||||||
|
|
||||||
|
import com.zbkj.common.config.CrmebConfig;
|
||||||
import com.zbkj.common.interceptor.SwaggerInterceptor;
|
import com.zbkj.common.interceptor.SwaggerInterceptor;
|
||||||
import com.zbkj.front.filter.ResponseFilter;
|
import com.zbkj.front.filter.ResponseFilter;
|
||||||
import com.zbkj.front.interceptor.FrontTokenInterceptor;
|
import com.zbkj.front.interceptor.FrontTokenInterceptor;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||||
|
|
@ -108,6 +110,9 @@ public class WebConfig implements WebMvcConfigurer {
|
||||||
excludePathPatterns("/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**");
|
excludePathPatterns("/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
CrmebConfig crmebConfig;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||||
registry.addResourceHandler("/**")
|
registry.addResourceHandler("/**")
|
||||||
|
|
@ -116,6 +121,27 @@ public class WebConfig implements WebMvcConfigurer {
|
||||||
.addResourceLocations("classpath:/META-INF/resources/");
|
.addResourceLocations("classpath:/META-INF/resources/");
|
||||||
registry.addResourceHandler("/webjars/**")
|
registry.addResourceHandler("/webjars/**")
|
||||||
.addResourceLocations("classpath:/META-INF/resources/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
|
@Bean
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,26 @@ public class CommunityFrontController {
|
||||||
return CommonResult.success(result);
|
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("获取可匹配用户总数")
|
@ApiOperation("获取可匹配用户总数")
|
||||||
@GetMapping("/user-count")
|
@GetMapping("/user-count")
|
||||||
public CommonResult<Integer> getMatchableUserCount() {
|
public CommonResult<Integer> getMatchableUserCount() {
|
||||||
|
|
|
||||||
|
|
@ -11,10 +11,12 @@ import com.zbkj.common.request.*;
|
||||||
import com.zbkj.common.response.*;
|
import com.zbkj.common.response.*;
|
||||||
import com.zbkj.common.result.CommonResult;
|
import com.zbkj.common.result.CommonResult;
|
||||||
import com.zbkj.common.token.FrontTokenComponent;
|
import com.zbkj.common.token.FrontTokenComponent;
|
||||||
|
import com.zbkj.common.vo.FileResultVo;
|
||||||
import com.zbkj.front.service.UserCenterService;
|
import com.zbkj.front.service.UserCenterService;
|
||||||
import com.zbkj.service.service.SystemGroupDataService;
|
import com.zbkj.service.service.SystemGroupDataService;
|
||||||
import com.zbkj.service.service.UserService;
|
import com.zbkj.service.service.UserService;
|
||||||
import com.zbkj.service.service.FollowRecordService;
|
import com.zbkj.service.service.FollowRecordService;
|
||||||
|
import com.zbkj.service.service.UploadService;
|
||||||
import io.swagger.annotations.Api;
|
import io.swagger.annotations.Api;
|
||||||
import io.swagger.annotations.ApiImplicitParam;
|
import io.swagger.annotations.ApiImplicitParam;
|
||||||
import io.swagger.annotations.ApiOperation;
|
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.jdbc.core.JdbcTemplate;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
@ -65,6 +68,9 @@ public class UserController {
|
||||||
@Autowired
|
@Autowired
|
||||||
private FollowRecordService followRecordService;
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 个人中心-用户信息
|
* 个人中心-用户信息
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -27,23 +27,9 @@
|
||||||
|
|
||||||
<!-- 控制台输出 -->
|
<!-- 控制台输出 -->
|
||||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
|
||||||
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
<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>%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>
|
|
||||||
<charset>utf-8</charset>
|
<charset>utf-8</charset>
|
||||||
</encoder>
|
</encoder>
|
||||||
</appender>
|
</appender>
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,11 @@ public interface CommunityService {
|
||||||
*/
|
*/
|
||||||
List<NearbyUserVO> getNearbyUsers(Double latitude, Double longitude, Integer radius, Integer limit);
|
List<NearbyUserVO> getNearbyUsers(Double latitude, Double longitude, Integer radius, Integer limit);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取附近用户(简化版 - 只返回头像、昵称、地址)
|
||||||
|
*/
|
||||||
|
List<com.zbkj.common.vo.NearbyUserSimpleVO> getNearbyUsersSimple(Integer limit);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取可匹配用户总数(排除当前用户)
|
* 获取可匹配用户总数(排除当前用户)
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,13 @@ package com.zbkj.service.service;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.extension.service.IService;
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
import com.zbkj.common.model.gift.GiftRecord;
|
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.request.SendGiftRequest;
|
||||||
import com.zbkj.common.response.SendGiftResponse;
|
import com.zbkj.common.response.SendGiftResponse;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 礼物赠送记录服务接口
|
* 礼物赠送记录服务接口
|
||||||
|
|
@ -39,4 +42,17 @@ public interface GiftRecordService extends IService<GiftRecord> {
|
||||||
* @return 总消费钻石数
|
* @return 总消费钻石数
|
||||||
*/
|
*/
|
||||||
Long getTotalDiamondByUser(Integer roomId, Integer userId);
|
Long getTotalDiamondByUser(Integer roomId, Integer userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询礼物记录(带用户信息)
|
||||||
|
* @param request 搜索请求
|
||||||
|
* @return 分页结果
|
||||||
|
*/
|
||||||
|
CommonPage<Map<String, Object>> getGiftRecordList(GiftRecordSearchRequest request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取礼物记录统计数据
|
||||||
|
* @return 统计数据
|
||||||
|
*/
|
||||||
|
Map<String, Object> getGiftRecordStatistics();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,11 @@ package com.zbkj.service.service;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.extension.service.IService;
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
import com.zbkj.common.model.gift.Gift;
|
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.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 礼物服务接口
|
* 礼物服务接口
|
||||||
|
|
@ -19,4 +22,29 @@ public interface GiftService extends IService<Gift> {
|
||||||
* 根据ID获取礼物
|
* 根据ID获取礼物
|
||||||
*/
|
*/
|
||||||
Gift getGiftById(Integer giftId);
|
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();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,9 @@ import com.zbkj.service.dao.CommunityMatchConfigDao;
|
||||||
import com.zbkj.service.dao.CommunityMessageDao;
|
import com.zbkj.service.dao.CommunityMessageDao;
|
||||||
import com.zbkj.service.service.CommunityService;
|
import com.zbkj.service.service.CommunityService;
|
||||||
import com.zbkj.service.service.UserService;
|
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.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
|
@ -30,6 +33,8 @@ import java.util.List;
|
||||||
@Service
|
@Service
|
||||||
public class CommunityServiceImpl implements CommunityService {
|
public class CommunityServiceImpl implements CommunityService {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(CommunityServiceImpl.class);
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private CommunityCategoryDao categoryDao;
|
private CommunityCategoryDao categoryDao;
|
||||||
|
|
||||||
|
|
@ -213,10 +218,11 @@ public class CommunityServiceImpl implements CommunityService {
|
||||||
|
|
||||||
// 处理地址:优先使用addres字段,否则使用country
|
// 处理地址:优先使用addres字段,否则使用country
|
||||||
String location = user.getAddres();
|
String location = user.getAddres();
|
||||||
if (location == null || location.isEmpty()) {
|
if (location == null || location.isEmpty() || "CN".equals(location)) {
|
||||||
location = user.getCountry();
|
location = user.getCountry();
|
||||||
}
|
}
|
||||||
if (location == null || location.isEmpty()) {
|
// 如果country也是CN或为空,显示"附近"
|
||||||
|
if (location == null || location.isEmpty() || "CN".equals(location) || "China".equals(location)) {
|
||||||
location = "附近";
|
location = "附近";
|
||||||
}
|
}
|
||||||
// 简化地址显示(只取城市部分)
|
// 简化地址显示(只取城市部分)
|
||||||
|
|
@ -291,4 +297,164 @@ public class CommunityServiceImpl implements CommunityService {
|
||||||
message.setAuditRemark("自动审核通过");
|
message.setAuditRemark("自动审核通过");
|
||||||
message.setAuditType(0);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,14 @@
|
||||||
package com.zbkj.service.service.impl;
|
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.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
import com.zbkj.common.exception.CrmebException;
|
import com.zbkj.common.exception.CrmebException;
|
||||||
import com.zbkj.common.model.gift.Gift;
|
import com.zbkj.common.model.gift.Gift;
|
||||||
import com.zbkj.common.model.gift.GiftRecord;
|
import com.zbkj.common.model.gift.GiftRecord;
|
||||||
import com.zbkj.common.model.user.User;
|
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.request.SendGiftRequest;
|
||||||
import com.zbkj.common.response.SendGiftResponse;
|
import com.zbkj.common.response.SendGiftResponse;
|
||||||
import com.zbkj.service.dao.GiftRecordDao;
|
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.GiftService;
|
||||||
import com.zbkj.service.service.UserService;
|
import com.zbkj.service.service.UserService;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.Date;
|
import java.util.*;
|
||||||
import java.util.List;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 礼物赠送记录服务实现
|
* 礼物赠送记录服务实现
|
||||||
|
|
@ -129,4 +134,112 @@ public class GiftRecordServiceImpl extends ServiceImpl<GiftRecordDao, GiftRecord
|
||||||
public Long getTotalDiamondByUser(Integer roomId, Integer userId) {
|
public Long getTotalDiamondByUser(Integer roomId, Integer userId) {
|
||||||
return giftRecordDao.getTotalDiamondByUser(roomId, 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,19 @@
|
||||||
package com.zbkj.service.service.impl;
|
package com.zbkj.service.service.impl;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
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.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
import com.zbkj.common.model.gift.Gift;
|
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.dao.GiftDao;
|
||||||
import com.zbkj.service.service.GiftService;
|
import com.zbkj.service.service.GiftService;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.util.List;
|
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() {
|
public List<Gift> getActiveGifts() {
|
||||||
LambdaQueryWrapper<Gift> wrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<Gift> wrapper = new LambdaQueryWrapper<>();
|
||||||
wrapper.eq(Gift::getStatus, 1);
|
wrapper.eq(Gift::getStatus, 1);
|
||||||
|
wrapper.orderByAsc(Gift::getSort, Gift::getId);
|
||||||
return list(wrapper);
|
return list(wrapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -26,4 +33,84 @@ public class GiftServiceImpl extends ServiceImpl<GiftDao, Gift> implements GiftS
|
||||||
public Gift getGiftById(Integer giftId) {
|
public Gift getGiftById(Integer giftId) {
|
||||||
return getById(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<>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -87,10 +87,33 @@ public class UploadServiceImpl implements UploadService {
|
||||||
public FileResultVo imageUpload(MultipartFile multipartFile, String model, Integer pid) {
|
public FileResultVo imageUpload(MultipartFile multipartFile, String model, Integer pid) {
|
||||||
FileResultVo fileResultVo = new FileResultVo();
|
FileResultVo fileResultVo = new FileResultVo();
|
||||||
try {
|
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);
|
fileResultVo = commonUpload(multipartFile, model, pid, UploadConstants.UPLOAD_FILE_KEYWORD);
|
||||||
|
|
||||||
|
logger.info("图片上传成功,URL: {}", fileResultVo.getUrl());
|
||||||
|
logger.info("==================");
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.error("图片上传IO异常,{}", e.getMessage());
|
logger.error("=== 图片上传IO异常 ===");
|
||||||
throw new CrmebException("图片上传 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;
|
return fileResultVo;
|
||||||
}
|
}
|
||||||
|
|
@ -171,6 +194,9 @@ public class UploadServiceImpl implements UploadService {
|
||||||
if (ObjectUtil.isNull(multipartFile) || multipartFile.isEmpty()) {
|
if (ObjectUtil.isNull(multipartFile) || multipartFile.isEmpty()) {
|
||||||
throw new CrmebException(CommonResultCode.VALIDATE_FAILED, "上载的文件对象不存在...");
|
throw new CrmebException(CommonResultCode.VALIDATE_FAILED, "上载的文件对象不存在...");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.info("=== commonUpload 开始 ===");
|
||||||
|
|
||||||
// 校验
|
// 校验
|
||||||
String fileName = multipartFile.getOriginalFilename();
|
String fileName = multipartFile.getOriginalFilename();
|
||||||
float fileSize = (float) multipartFile.getSize() / 1024 / 1024;
|
float fileSize = (float) multipartFile.getSize() / 1024 / 1024;
|
||||||
|
|
@ -182,19 +208,31 @@ public class UploadServiceImpl implements UploadService {
|
||||||
|
|
||||||
// 服务器存储地址 - 使用绝对路径
|
// 服务器存储地址 - 使用绝对路径
|
||||||
String rootPath = crmebConfig.getAbsoluteImagePath();
|
String rootPath = crmebConfig.getAbsoluteImagePath();
|
||||||
|
logger.info("根路径: {}", rootPath);
|
||||||
|
|
||||||
// 模块
|
// 模块
|
||||||
String modelPath = "public/" + model + "/";
|
String modelPath = "public/" + model + "/";
|
||||||
|
logger.info("模块路径: {}", modelPath);
|
||||||
|
|
||||||
// 类型
|
// 类型
|
||||||
String type = (fileType.equals(UploadConstants.UPLOAD_FILE_KEYWORD) ? UploadConstants.UPLOAD_FILE_KEYWORD : UploadConstants.UPLOAD_AFTER_FILE_KEYWORD) + "/";
|
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);
|
String newFileName = UploadUtil.fileName(extName);
|
||||||
|
logger.info("新文件名: {}", newFileName);
|
||||||
|
|
||||||
// 创建目标文件的名称,规则: 子目录/年/月/日.后缀名
|
// 创建目标文件的名称,规则: 子目录/年/月/日.后缀名
|
||||||
String webPath = type + modelPath + CrmebDateUtil.nowDate("yyyy/MM/dd") + "/";
|
String webPath = type + modelPath + CrmebDateUtil.nowDate("yyyy/MM/dd") + "/";
|
||||||
|
logger.info("Web路径: {}", webPath);
|
||||||
|
|
||||||
// 文件分隔符转化为当前系统的格式
|
// 文件分隔符转化为当前系统的格式
|
||||||
String destPath = FilenameUtils.separatorsToSystem(rootPath + webPath) + newFileName;
|
String destPath = FilenameUtils.separatorsToSystem(rootPath + webPath) + newFileName;
|
||||||
|
logger.info("目标完整路径: {}", destPath);
|
||||||
|
|
||||||
// 创建文件
|
// 创建文件
|
||||||
File file = UploadUtil.createFile(destPath);
|
File file = UploadUtil.createFile(destPath);
|
||||||
|
logger.info("文件对象创建成功: {}", file.getAbsolutePath());
|
||||||
|
|
||||||
// 拼装返回的数据
|
// 拼装返回的数据
|
||||||
FileResultVo resultFile = new FileResultVo();
|
FileResultVo resultFile = new FileResultVo();
|
||||||
|
|
@ -221,16 +259,41 @@ public class UploadServiceImpl implements UploadService {
|
||||||
//图片上传类型 1本地 2七牛云 3OSS 4COS, 默认本地
|
//图片上传类型 1本地 2七牛云 3OSS 4COS, 默认本地
|
||||||
String uploadType = systemConfigService.getValueByKeyException(SysConfigConstants.CONFIG_UPLOAD_TYPE);
|
String uploadType = systemConfigService.getValueByKeyException(SysConfigConstants.CONFIG_UPLOAD_TYPE);
|
||||||
Integer uploadTypeInt = Integer.parseInt(uploadType);
|
Integer uploadTypeInt = Integer.parseInt(uploadType);
|
||||||
|
logger.info("上传类型: {}", uploadTypeInt);
|
||||||
|
|
||||||
if (uploadTypeInt.equals(1)) {
|
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);
|
systemAttachmentService.save(systemAttachment);
|
||||||
return resultFile;
|
return resultFile;
|
||||||
}
|
}
|
||||||
CloudVo cloudVo = new CloudVo();
|
CloudVo cloudVo = new CloudVo();
|
||||||
// 判断是否保存本地
|
// 判断是否保存本地
|
||||||
String fileIsSave = systemConfigService.getValueByKeyException(SysConfigConstants.CONFIG_FILE_IS_SAVE);
|
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) {
|
switch (uploadTypeInt) {
|
||||||
case 2:
|
case 2:
|
||||||
systemAttachment.setImageType(2);
|
systemAttachment.setImageType(2);
|
||||||
|
|
|
||||||
|
|
@ -1747,38 +1747,115 @@ public class UserServiceImpl extends ServiceImpl<UserDao, User> implements UserS
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Boolean editUser(UserEditRequest request) {
|
public Boolean editUser(UserEditRequest request) {
|
||||||
User user = getInfo();
|
logger.info("========== editUser 开始 ==========");
|
||||||
|
logger.info("请求参数: {}", request);
|
||||||
|
|
||||||
// 更新头像(如果提供)
|
User user = getInfo();
|
||||||
if (request.getAvatar() != null && !request.getAvatar().isEmpty()) {
|
if (user == null) {
|
||||||
user.setAvatar(systemAttachmentService.clearPrefix(request.getAvatar()));
|
logger.error("获取当前用户失败,用户未登录或token失效");
|
||||||
|
throw new CrmebException("用户未登录或登录已过期");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新昵称
|
logger.info("当前用户ID: {}, 昵称: {}", user.getUid(), user.getNickname());
|
||||||
user.setNickname(request.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());
|
user.setBirthday(request.getBirthday());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新性别(如果提供)
|
// 更新性别(如果提供)
|
||||||
if (request.getSex() != null) {
|
if (request.getSex() != null) {
|
||||||
|
logger.info("更新性别: {}", request.getSex());
|
||||||
user.setSex(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());
|
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) {
|
if (request.getMark() != null) {
|
||||||
|
logger.info("更新个人签名: {}", request.getMark());
|
||||||
user.setMark(request.getMark());
|
user.setMark(request.getMark());
|
||||||
}
|
}
|
||||||
|
|
||||||
user.setUpdateTime(DateUtil.date());
|
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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,9 @@
|
||||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
<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+ 需要此权限来显示来电全屏界面 -->
|
<!-- Android 10+ 需要此权限来显示来电全屏界面 -->
|
||||||
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
|
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
|
||||||
<!-- 前台服务权限,用于保持通话连接 -->
|
<!-- 前台服务权限,用于保持通话连接 -->
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import com.bumptech.glide.Glide;
|
import com.bumptech.glide.Glide;
|
||||||
import com.example.livestreaming.databinding.ActivityEditProfileBinding;
|
import com.example.livestreaming.databinding.ActivityEditProfileBinding;
|
||||||
|
import com.example.livestreaming.location.TianDiTuLocationService;
|
||||||
import com.example.livestreaming.net.ApiClient;
|
import com.example.livestreaming.net.ApiClient;
|
||||||
import com.example.livestreaming.net.ApiResponse;
|
import com.example.livestreaming.net.ApiResponse;
|
||||||
import com.example.livestreaming.net.ApiService;
|
import com.example.livestreaming.net.ApiService;
|
||||||
|
|
@ -60,6 +61,7 @@ public class EditProfileActivity extends AppCompatActivity {
|
||||||
private static final String TAG = "EditProfileActivity";
|
private static final String TAG = "EditProfileActivity";
|
||||||
private ActivityEditProfileBinding binding;
|
private ActivityEditProfileBinding binding;
|
||||||
private ApiService apiService;
|
private ApiService apiService;
|
||||||
|
private TianDiTuLocationService locationService;
|
||||||
|
|
||||||
private static final String PREFS_NAME = "profile_prefs";
|
private static final String PREFS_NAME = "profile_prefs";
|
||||||
private static final String KEY_NAME = "profile_name";
|
private static final String KEY_NAME = "profile_name";
|
||||||
|
|
@ -81,6 +83,11 @@ public class EditProfileActivity extends AppCompatActivity {
|
||||||
private ActivityResultLauncher<String> pickImageLauncher;
|
private ActivityResultLauncher<String> pickImageLauncher;
|
||||||
private ActivityResultLauncher<Uri> takePictureLauncher;
|
private ActivityResultLauncher<Uri> takePictureLauncher;
|
||||||
private ActivityResultLauncher<String> requestCameraPermissionLauncher;
|
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) {
|
public static void start(Context context) {
|
||||||
Intent intent = new Intent(context, EditProfileActivity.class);
|
Intent intent = new Intent(context, EditProfileActivity.class);
|
||||||
|
|
@ -101,6 +108,7 @@ public class EditProfileActivity extends AppCompatActivity {
|
||||||
setContentView(binding.getRoot());
|
setContentView(binding.getRoot());
|
||||||
|
|
||||||
apiService = ApiClient.getService(this);
|
apiService = ApiClient.getService(this);
|
||||||
|
locationService = new TianDiTuLocationService(this);
|
||||||
|
|
||||||
pickImageLauncher = registerForActivityResult(new ActivityResultContracts.GetContent(), uri -> {
|
pickImageLauncher = registerForActivityResult(new ActivityResultContracts.GetContent(), uri -> {
|
||||||
if (uri == null) return;
|
if (uri == null) return;
|
||||||
|
|
@ -126,14 +134,44 @@ public class EditProfileActivity extends AppCompatActivity {
|
||||||
if (pendingCameraUri == null) return;
|
if (pendingCameraUri == null) return;
|
||||||
takePictureLauncher.launch(pendingCameraUri);
|
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.backButton.setOnClickListener(v -> finish());
|
||||||
binding.cancelButton.setOnClickListener(v -> finish());
|
binding.cancelButton.setOnClickListener(v -> finish());
|
||||||
|
|
||||||
loadFromPrefs();
|
loadFromPrefs();
|
||||||
setupGenderDropdown();
|
setupGenderDropdown();
|
||||||
setupLocationDropdown();
|
// 所在地已删除,不再需要setupLocationDropdown
|
||||||
setupBirthdayPicker();
|
setupBirthdayPicker();
|
||||||
|
|
||||||
|
// 不再在编辑资料页面自动获取位置
|
||||||
|
// requestLocationAndUpdate();
|
||||||
|
|
||||||
binding.avatarRow.setOnClickListener(v -> showAvatarBottomSheet());
|
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 bio = binding.inputBio.getText() != null ? binding.inputBio.getText().toString().trim() : "";
|
||||||
String birthday = binding.inputBirthday.getText() != null ? binding.inputBirthday.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 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)) {
|
if (TextUtils.isEmpty(name)) {
|
||||||
Toast.makeText(this, "昵称不能为空", Toast.LENGTH_SHORT).show();
|
Toast.makeText(this, "昵称不能为空", Toast.LENGTH_SHORT).show();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果选择了新头像,先上传头像
|
isUploading = true;
|
||||||
if (selectedAvatarUri != null && uploadedAvatarUrl == null) {
|
binding.saveButton.setEnabled(false);
|
||||||
isUploading = true;
|
|
||||||
binding.saveButton.setEnabled(false);
|
// 检查是否有新选择的本地头像
|
||||||
Toast.makeText(this, "正在上传头像...", Toast.LENGTH_SHORT).show();
|
boolean hasNewAvatar = selectedAvatarUri != null && uploadedAvatarUrl == null;
|
||||||
|
if (hasNewAvatar) {
|
||||||
uploadAvatar(selectedAvatarUri, avatarUrl -> {
|
String scheme = selectedAvatarUri.getScheme();
|
||||||
uploadedAvatarUrl = avatarUrl;
|
if ("http".equalsIgnoreCase(scheme) || "https".equalsIgnoreCase(scheme)) {
|
||||||
// 头像上传成功后,更新用户资料
|
hasNewAvatar = false; // 网络URL不算新头像
|
||||||
updateUserProfile(name, bio, birthday, gender, location, avatarUrl);
|
}
|
||||||
}, error -> {
|
}
|
||||||
isUploading = false;
|
|
||||||
binding.saveButton.setEnabled(true);
|
if (hasNewAvatar) {
|
||||||
Toast.makeText(this, "头像上传失败: " + error, Toast.LENGTH_SHORT).show();
|
// 有新头像,使用multipart接口
|
||||||
});
|
updateUserProfileWithFile(selectedAvatarUri, name, bio, birthday, gender);
|
||||||
} else {
|
} else {
|
||||||
// 没有新头像,直接更新资料
|
// 没有新头像,使用普通接口
|
||||||
String existingAvatarUrl = getSharedPreferences(PREFS_NAME, MODE_PRIVATE).getString(KEY_AVATAR_URL, null);
|
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 {
|
try {
|
||||||
|
Log.d(TAG, "开始上传头像并保存资料,URI: " + avatarUri);
|
||||||
|
|
||||||
// 获取文件名
|
// 获取文件名
|
||||||
String fileName = getFileName(imageUri);
|
String fileName = getFileName(avatarUri);
|
||||||
if (fileName == null) {
|
if (fileName == null) {
|
||||||
fileName = "avatar_" + System.currentTimeMillis() + ".jpg";
|
fileName = "avatar_" + System.currentTimeMillis() + ".jpg";
|
||||||
}
|
}
|
||||||
|
|
||||||
// 读取文件内容
|
// 读取文件内容
|
||||||
InputStream inputStream = getContentResolver().openInputStream(imageUri);
|
inputStream = getContentResolver().openInputStream(avatarUri);
|
||||||
if (inputStream == null) {
|
if (inputStream == null) {
|
||||||
onError.onError("无法读取图片文件");
|
isUploading = false;
|
||||||
|
binding.saveButton.setEnabled(true);
|
||||||
|
Toast.makeText(this, "无法读取图片文件", Toast.LENGTH_SHORT).show();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 读取所有字节
|
// 读取所有字节到内存
|
||||||
java.io.ByteArrayOutputStream buffer = new java.io.ByteArrayOutputStream();
|
java.io.ByteArrayOutputStream buffer = new java.io.ByteArrayOutputStream();
|
||||||
byte[] data = new byte[8192];
|
byte[] data = new byte[8192];
|
||||||
int nRead;
|
int nRead;
|
||||||
|
|
@ -238,48 +280,199 @@ public class EditProfileActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
buffer.flush();
|
buffer.flush();
|
||||||
byte[] fileBytes = buffer.toByteArray();
|
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);
|
RequestBody requestFile = RequestBody.create(MediaType.parse("image/*"), fileBytes);
|
||||||
MultipartBody.Part body = MultipartBody.Part.createFormData("file", fileName, requestFile);
|
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表示前台用户
|
RequestBody pid = RequestBody.create(MediaType.parse("text/plain"), "7"); // 7表示前台用户
|
||||||
|
|
||||||
|
Log.d(TAG, "开始调用上传接口...");
|
||||||
|
|
||||||
// 调用上传接口
|
// 调用上传接口
|
||||||
apiService.uploadImage(body, model, pid).enqueue(new Callback<ApiResponse<FileUploadResponse>>() {
|
apiService.uploadImage(body, model, pid).enqueue(new Callback<ApiResponse<FileUploadResponse>>() {
|
||||||
@Override
|
@Override
|
||||||
public void onResponse(Call<ApiResponse<FileUploadResponse>> call, Response<ApiResponse<FileUploadResponse>> response) {
|
public void onResponse(Call<ApiResponse<FileUploadResponse>> call, Response<ApiResponse<FileUploadResponse>> response) {
|
||||||
if (response.isSuccessful() && response.body() != null && response.body().isOk()) {
|
Log.d(TAG, "上传接口响应,状态码: " + response.code());
|
||||||
FileUploadResponse data = response.body().getData();
|
|
||||||
if (data != null && data.getUrl() != null) {
|
if (response.isSuccessful() && response.body() != null) {
|
||||||
Log.d(TAG, "头像上传成功: " + data.getUrl());
|
Log.d(TAG, "响应体: " + response.body());
|
||||||
onSuccess.onSuccess(data.getUrl());
|
|
||||||
|
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 {
|
} else {
|
||||||
onError.onError("服务器返回数据异常");
|
String msg = response.body().getMessage();
|
||||||
|
Log.e(TAG, "服务器返回错误: " + msg);
|
||||||
|
onError.onError(msg != null ? msg : "上传失败");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
String msg = response.body() != null ? response.body().getMessage() : "上传失败";
|
String errorMsg = "HTTP " + response.code();
|
||||||
onError.onError(msg);
|
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
|
@Override
|
||||||
public void onFailure(Call<ApiResponse<FileUploadResponse>> call, Throwable t) {
|
public void onFailure(Call<ApiResponse<FileUploadResponse>> call, Throwable t) {
|
||||||
Log.e(TAG, "头像上传网络错误: " + t.getMessage());
|
Log.e(TAG, "头像上传网络错误", t);
|
||||||
onError.onError("网络错误: " + t.getMessage());
|
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) {
|
} catch (Exception e) {
|
||||||
Log.e(TAG, "头像上传异常: " + e.getMessage());
|
Log.e(TAG, "上传异常", e);
|
||||||
onError.onError("上传异常: " + e.getMessage());
|
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();
|
UserEditRequest request = new UserEditRequest();
|
||||||
request.setNickname(name);
|
request.setNickname(name);
|
||||||
if (avatarUrl != null) {
|
if (avatarUrl != null) {
|
||||||
|
|
@ -296,9 +489,7 @@ public class EditProfileActivity extends AppCompatActivity {
|
||||||
else if ("保密".equals(gender)) sex = 3;
|
else if ("保密".equals(gender)) sex = 3;
|
||||||
request.setSex(sex);
|
request.setSex(sex);
|
||||||
}
|
}
|
||||||
if (!TextUtils.isEmpty(location)) {
|
// 不再设置addres,由系统自动获取
|
||||||
request.setAddres(location);
|
|
||||||
}
|
|
||||||
if (!TextUtils.isEmpty(bio) && !BIO_HINT_TEXT.equals(bio)) {
|
if (!TextUtils.isEmpty(bio) && !BIO_HINT_TEXT.equals(bio)) {
|
||||||
request.setMark(bio);
|
request.setMark(bio);
|
||||||
}
|
}
|
||||||
|
|
@ -311,8 +502,8 @@ public class EditProfileActivity extends AppCompatActivity {
|
||||||
|
|
||||||
if (response.isSuccessful() && response.body() != null && response.body().isOk()) {
|
if (response.isSuccessful() && response.body() != null && response.body().isOk()) {
|
||||||
Log.d(TAG, "用户资料更新成功");
|
Log.d(TAG, "用户资料更新成功");
|
||||||
// 保存到本地
|
// 保存到本地(不包含location)
|
||||||
saveToLocalPrefs(name, bio, birthday, gender, location, avatarUrl);
|
saveToLocalPrefs(name, bio, birthday, gender, avatarUrl);
|
||||||
Toast.makeText(EditProfileActivity.this, "保存成功", Toast.LENGTH_SHORT).show();
|
Toast.makeText(EditProfileActivity.this, "保存成功", Toast.LENGTH_SHORT).show();
|
||||||
setResult(RESULT_OK);
|
setResult(RESULT_OK);
|
||||||
finish();
|
finish();
|
||||||
|
|
@ -320,7 +511,7 @@ public class EditProfileActivity extends AppCompatActivity {
|
||||||
String msg = response.body() != null ? response.body().getMessage() : "更新失败";
|
String msg = response.body() != null ? response.body().getMessage() : "更新失败";
|
||||||
Log.e(TAG, "用户资料更新失败: " + msg);
|
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();
|
Toast.makeText(EditProfileActivity.this, "已保存到本地,服务器同步失败: " + msg, Toast.LENGTH_SHORT).show();
|
||||||
setResult(RESULT_OK);
|
setResult(RESULT_OK);
|
||||||
finish();
|
finish();
|
||||||
|
|
@ -333,7 +524,7 @@ public class EditProfileActivity extends AppCompatActivity {
|
||||||
binding.saveButton.setEnabled(true);
|
binding.saveButton.setEnabled(true);
|
||||||
Log.e(TAG, "用户资料更新网络错误: " + t.getMessage());
|
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();
|
Toast.makeText(EditProfileActivity.this, "已保存到本地,网络同步失败", Toast.LENGTH_SHORT).show();
|
||||||
setResult(RESULT_OK);
|
setResult(RESULT_OK);
|
||||||
finish();
|
finish();
|
||||||
|
|
@ -344,7 +535,7 @@ public class EditProfileActivity extends AppCompatActivity {
|
||||||
/**
|
/**
|
||||||
* 保存到本地SharedPreferences
|
* 保存到本地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();
|
android.content.SharedPreferences.Editor editor = getSharedPreferences(PREFS_NAME, MODE_PRIVATE).edit();
|
||||||
|
|
||||||
editor.putString(KEY_NAME, name);
|
editor.putString(KEY_NAME, name);
|
||||||
|
|
@ -367,11 +558,7 @@ public class EditProfileActivity extends AppCompatActivity {
|
||||||
editor.putString(KEY_GENDER, gender);
|
editor.putString(KEY_GENDER, gender);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TextUtils.isEmpty(location)) {
|
// 不再保存location到本地,由系统自动获取
|
||||||
editor.remove(KEY_LOCATION);
|
|
||||||
} else {
|
|
||||||
editor.putString(KEY_LOCATION, location);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (avatarUrl != null) {
|
if (avatarUrl != null) {
|
||||||
editor.putString(KEY_AVATAR_URL, avatarUrl);
|
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 gender = getSharedPreferences(PREFS_NAME, MODE_PRIVATE).getString(KEY_GENDER, "");
|
||||||
String location = getSharedPreferences(PREFS_NAME, MODE_PRIVATE).getString(KEY_LOCATION, "");
|
String location = getSharedPreferences(PREFS_NAME, MODE_PRIVATE).getString(KEY_LOCATION, "");
|
||||||
String avatarUri = getSharedPreferences(PREFS_NAME, MODE_PRIVATE).getString(KEY_AVATAR_URI, null);
|
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);
|
binding.inputName.setText(name);
|
||||||
if (!TextUtils.isEmpty(bio) && !BIO_HINT_TEXT.equals(bio)) {
|
if (!TextUtils.isEmpty(bio) && !BIO_HINT_TEXT.equals(bio)) {
|
||||||
|
|
@ -478,11 +666,23 @@ public class EditProfileActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
binding.inputBirthday.setText(birthday);
|
binding.inputBirthday.setText(birthday);
|
||||||
binding.inputGender.setText(gender);
|
binding.inputGender.setText(gender);
|
||||||
binding.inputLocation.setText(location);
|
// 不再加载location,已删除该输入框
|
||||||
|
|
||||||
|
// 优先使用本地URI,如果没有则使用服务器URL
|
||||||
if (!TextUtils.isEmpty(avatarUri)) {
|
if (!TextUtils.isEmpty(avatarUri)) {
|
||||||
selectedAvatarUri = Uri.parse(avatarUri);
|
Uri uri = Uri.parse(avatarUri);
|
||||||
Glide.with(this).load(selectedAvatarUri).circleCrop().into(binding.avatarPreview);
|
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 {
|
} else {
|
||||||
int avatarRes = getSharedPreferences(PREFS_NAME, MODE_PRIVATE).getInt(KEY_AVATAR_RES, 0);
|
int avatarRes = getSharedPreferences(PREFS_NAME, MODE_PRIVATE).getInt(KEY_AVATAR_RES, 0);
|
||||||
if (avatarRes != 0) {
|
if (avatarRes != 0) {
|
||||||
|
|
@ -502,7 +702,8 @@ public class EditProfileActivity extends AppCompatActivity {
|
||||||
|
|
||||||
pick.setOnClickListener(v -> {
|
pick.setOnClickListener(v -> {
|
||||||
dialog.dismiss();
|
dialog.dismiss();
|
||||||
pickImageLauncher.launch("image/*");
|
isPickingFromGallery = true;
|
||||||
|
checkStoragePermissionAndPickImage();
|
||||||
});
|
});
|
||||||
|
|
||||||
camera.setOnClickListener(v -> {
|
camera.setOnClickListener(v -> {
|
||||||
|
|
@ -526,6 +727,35 @@ public class EditProfileActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
requestCameraPermissionLauncher.launch(Manifest.permission.CAMERA);
|
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() {
|
private Uri createTempCameraUri() {
|
||||||
try {
|
try {
|
||||||
|
|
@ -557,9 +787,10 @@ public class EditProfileActivity extends AppCompatActivity {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupLocationDropdown() {
|
// 移除手动选择地址功能,将通过天地图API自动获取用户位置
|
||||||
binding.inputLocation.setOnClickListener(v -> showLocationPicker());
|
// private void setupLocationDropdown() {
|
||||||
}
|
// binding.inputLocation.setOnClickListener(v -> showLocationPicker());
|
||||||
|
// }
|
||||||
|
|
||||||
private void showLocationPicker() {
|
private void showLocationPicker() {
|
||||||
BottomSheetDialog dialog = new BottomSheetDialog(this);
|
BottomSheetDialog dialog = new BottomSheetDialog(this);
|
||||||
|
|
@ -575,8 +806,8 @@ public class EditProfileActivity extends AppCompatActivity {
|
||||||
LocationDataManager locationManager = LocationDataManager.getInstance();
|
LocationDataManager locationManager = LocationDataManager.getInstance();
|
||||||
locationManager.loadData(this);
|
locationManager.loadData(this);
|
||||||
|
|
||||||
// 解析当前地址
|
// 解析当前地址(已移除手动输入,使用空字符串)
|
||||||
String currentLocation = binding.inputLocation.getText() != null ? binding.inputLocation.getText().toString() : "";
|
String currentLocation = "";
|
||||||
String[] parsed = locationManager.parseLocation(currentLocation);
|
String[] parsed = locationManager.parseLocation(currentLocation);
|
||||||
String currentProvince = parsed[0];
|
String currentProvince = parsed[0];
|
||||||
String currentCity = parsed[1];
|
String currentCity = parsed[1];
|
||||||
|
|
@ -631,8 +862,7 @@ public class EditProfileActivity extends AppCompatActivity {
|
||||||
});
|
});
|
||||||
|
|
||||||
confirmButton.setOnClickListener(v -> {
|
confirmButton.setOnClickListener(v -> {
|
||||||
String location = locationManager.formatLocation(selectedProvince[0], selectedCity[0]);
|
// 位置信息已移除手动输入,此功能不再使用
|
||||||
binding.inputLocation.setText(location);
|
|
||||||
dialog.dismiss();
|
dialog.dismiss();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -788,4 +1018,3 @@ public class EditProfileActivity extends AppCompatActivity {
|
||||||
return cal.getActualMaximum(Calendar.DAY_OF_MONTH);
|
return cal.getActualMaximum(Calendar.DAY_OF_MONTH);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -55,8 +55,10 @@ import com.example.livestreaming.net.StreamConfig;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import retrofit2.Call;
|
import retrofit2.Call;
|
||||||
import retrofit2.Callback;
|
import retrofit2.Callback;
|
||||||
|
|
@ -74,6 +76,7 @@ public class MainActivity extends AppCompatActivity {
|
||||||
private final List<Room> discoverRooms = new ArrayList<>(); // 发现页面的房间列表
|
private final List<Room> discoverRooms = new ArrayList<>(); // 发现页面的房间列表
|
||||||
private final List<NearbyUser> nearbyUsers = new ArrayList<>(); // 附近页面的用户列表
|
private final List<NearbyUser> nearbyUsers = new ArrayList<>(); // 附近页面的用户列表
|
||||||
private NearbyUsersAdapter nearbyUsersAdapter; // 附近页面的适配器
|
private NearbyUsersAdapter nearbyUsersAdapter; // 附近页面的适配器
|
||||||
|
private boolean isLoadingMoreNearby = false; // 是否正在加载更多附近用户
|
||||||
|
|
||||||
// 新Tab布局相关
|
// 新Tab布局相关
|
||||||
private RecommendUserAdapter recommendUserAdapter; // 关注页面推荐用户适配器
|
private RecommendUserAdapter recommendUserAdapter; // 关注页面推荐用户适配器
|
||||||
|
|
@ -772,11 +775,8 @@ public class MainActivity extends AppCompatActivity {
|
||||||
// 用户可能选择了"不再询问",提示去设置中开启
|
// 用户可能选择了"不再询问",提示去设置中开启
|
||||||
new AlertDialog.Builder(this)
|
new AlertDialog.Builder(this)
|
||||||
.setTitle("需要位置权限")
|
.setTitle("需要位置权限")
|
||||||
.setMessage("您已拒绝位置权限,如需使用附近功能,请在设置中手动开启位置权限。")
|
|
||||||
.setPositiveButton("去设置", (dialog, which) -> {
|
.setPositiveButton("去设置", (dialog, which) -> {
|
||||||
Intent intent = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
|
openAppSettings();
|
||||||
intent.setData(android.net.Uri.parse("package:" + getPackageName()));
|
|
||||||
startActivity(intent);
|
|
||||||
})
|
})
|
||||||
.setNegativeButton("取消", null)
|
.setNegativeButton("取消", null)
|
||||||
.show();
|
.show();
|
||||||
|
|
@ -2433,8 +2433,30 @@ public class MainActivity extends AppCompatActivity {
|
||||||
// 设置附近内容RecyclerView
|
// 设置附近内容RecyclerView
|
||||||
RecyclerView nearbyContentList = findViewById(R.id.nearbyContentList);
|
RecyclerView nearbyContentList = findViewById(R.id.nearbyContentList);
|
||||||
if (nearbyContentList != null && nearbyContentList.getAdapter() == null) {
|
if (nearbyContentList != null && nearbyContentList.getAdapter() == null) {
|
||||||
nearbyContentList.setLayoutManager(new LinearLayoutManager(this));
|
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
|
||||||
|
nearbyContentList.setLayoutManager(layoutManager);
|
||||||
nearbyContentList.setAdapter(nearbyUsersAdapter);
|
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
|
// 设置附近页面的SwipeRefreshLayout
|
||||||
|
|
@ -3117,12 +3139,15 @@ public class MainActivity extends AppCompatActivity {
|
||||||
};
|
};
|
||||||
handler.postDelayed(nearbyLoadingTimeoutRunnable, 3000);
|
handler.postDelayed(nearbyLoadingTimeoutRunnable, 3000);
|
||||||
|
|
||||||
// 从后端获取附近用户(获取更多用户以便随机筛选)
|
// 从后端获取附近用户(使用简化接口)
|
||||||
ApiClient.getService(getApplicationContext()).getNearbyUsers(0, 0, 50000, 50)
|
Log.d(TAG, "开始加载附近用户,调用接口: /api/front/community/nearby-users-simple");
|
||||||
.enqueue(new Callback<ApiResponse<CommunityResponse.NearbyUserList>>() {
|
ApiClient.getService(getApplicationContext()).getNearbyUsersSimple(100)
|
||||||
|
.enqueue(new Callback<ApiResponse<CommunityResponse.NearbyUserSimpleList>>() {
|
||||||
@Override
|
@Override
|
||||||
public void onResponse(Call<ApiResponse<CommunityResponse.NearbyUserList>> call,
|
public void onResponse(Call<ApiResponse<CommunityResponse.NearbyUserSimpleList>> call,
|
||||||
Response<ApiResponse<CommunityResponse.NearbyUserList>> response) {
|
Response<ApiResponse<CommunityResponse.NearbyUserSimpleList>> response) {
|
||||||
|
Log.d(TAG, "附近用户接口响应 - HTTP状态码: " + response.code());
|
||||||
|
|
||||||
// 取消超时回调
|
// 取消超时回调
|
||||||
if (nearbyLoadingTimeoutRunnable != null) {
|
if (nearbyLoadingTimeoutRunnable != null) {
|
||||||
handler.removeCallbacks(nearbyLoadingTimeoutRunnable);
|
handler.removeCallbacks(nearbyLoadingTimeoutRunnable);
|
||||||
|
|
@ -3134,49 +3159,102 @@ public class MainActivity extends AppCompatActivity {
|
||||||
nearbyLoadingContainer.setVisibility(View.GONE);
|
nearbyLoadingContainer.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.isSuccessful() && response.body() != null && response.body().isOk()) {
|
if (response.isSuccessful() && response.body() != null) {
|
||||||
CommunityResponse.NearbyUserList userList = response.body().getData();
|
Log.d(TAG, "响应成功 - isOk: " + response.body().isOk() + ", code: " + response.body().getCode());
|
||||||
if (userList != null && userList.getUsers() != null && !userList.getUsers().isEmpty()) {
|
|
||||||
nearbyUsers.clear();
|
if (response.body().isOk()) {
|
||||||
|
CommunityResponse.NearbyUserSimpleList userList = response.body().getData();
|
||||||
|
Log.d(TAG, "用户列表数据: " + (userList != null ? "不为空" : "为空"));
|
||||||
|
|
||||||
// 随机打乱用户列表
|
if (userList != null && userList.getUsers() != null) {
|
||||||
List<CommunityResponse.NearbyUser> shuffledUsers = new ArrayList<>(userList.getUsers());
|
Log.d(TAG, "用户数量: " + userList.getUsers().size());
|
||||||
Collections.shuffle(shuffledUsers);
|
|
||||||
|
if (!userList.getUsers().isEmpty()) {
|
||||||
// 最多取10名用户
|
nearbyUsers.clear();
|
||||||
int count = Math.min(10, shuffledUsers.size());
|
|
||||||
for (int i = 0; i < count; i++) {
|
// 不打乱顺序,保持后端返回的距离排序(从近到远)
|
||||||
CommunityResponse.NearbyUser user = shuffledUsers.get(i);
|
List<CommunityResponse.NearbyUserSimple> sortedUsers = userList.getUsers();
|
||||||
String location = user.location != null ? user.location : "";
|
|
||||||
nearbyUsers.add(new NearbyUser(String.valueOf(user.id), user.nickname, location, user.isOnline));
|
// 显示所有用户,不限制数量
|
||||||
}
|
for (CommunityResponse.NearbyUserSimple user : sortedUsers) {
|
||||||
|
String address = user.address != null ? user.address : "未设置位置";
|
||||||
if (nearbyUsersAdapter != null) {
|
String distanceText = user.distanceText != null ? user.distanceText : "";
|
||||||
nearbyUsersAdapter.submitList(new ArrayList<>(nearbyUsers));
|
|
||||||
}
|
// 调试日志:打印原始数据
|
||||||
|
Log.d(TAG, "用户数据 - ID: " + user.id + ", 昵称: " + user.nickname);
|
||||||
// 更新附近人数
|
Log.d(TAG, " 地址(address): " + user.address);
|
||||||
if (nearbyCountText != null) {
|
Log.d(TAG, " 距离(distanceText): " + user.distanceText);
|
||||||
nearbyCountText.setText("共" + nearbyUsers.size() + "人");
|
|
||||||
}
|
// 显示逻辑:
|
||||||
if (locationText != null) {
|
// 1. 如果有距离,显示:地址 · 距离
|
||||||
locationText.setText("已找到附近的人");
|
// 2. 如果没有距离,只显示:地址
|
||||||
}
|
String locationInfo;
|
||||||
|
if (!distanceText.isEmpty()) {
|
||||||
// 显示列表,隐藏空状态
|
locationInfo = address + " · " + distanceText;
|
||||||
if (emptyNearbyContainer != null) {
|
} else {
|
||||||
emptyNearbyContainer.setVisibility(View.GONE);
|
locationInfo = address;
|
||||||
}
|
}
|
||||||
if (nearbyList != null) {
|
Log.d(TAG, " 最终显示: " + locationInfo);
|
||||||
nearbyList.setVisibility(View.VISIBLE);
|
|
||||||
|
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 {
|
} else {
|
||||||
// 没有数据,显示空状态
|
// 请求失败
|
||||||
if (nearbyCountText != null) {
|
Log.e(TAG, "请求失败 - message: " + response.body().getMessage());
|
||||||
nearbyCountText.setText("");
|
|
||||||
}
|
|
||||||
if (locationText != null) {
|
if (locationText != null) {
|
||||||
locationText.setText("附近暂无用户");
|
locationText.setText("获取附近用户失败");
|
||||||
}
|
}
|
||||||
if (emptyNearbyContainer != null) {
|
if (emptyNearbyContainer != null) {
|
||||||
emptyNearbyContainer.setVisibility(View.VISIBLE);
|
emptyNearbyContainer.setVisibility(View.VISIBLE);
|
||||||
|
|
@ -3186,9 +3264,10 @@ public class MainActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 请求失败
|
// HTTP 请求失败
|
||||||
|
Log.e(TAG, "HTTP请求失败 - 状态码: " + response.code());
|
||||||
if (locationText != null) {
|
if (locationText != null) {
|
||||||
locationText.setText("获取附近用户失败");
|
locationText.setText("网络请求失败");
|
||||||
}
|
}
|
||||||
if (emptyNearbyContainer != null) {
|
if (emptyNearbyContainer != null) {
|
||||||
emptyNearbyContainer.setVisibility(View.VISIBLE);
|
emptyNearbyContainer.setVisibility(View.VISIBLE);
|
||||||
|
|
@ -3200,7 +3279,7 @@ public class MainActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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());
|
Log.e(TAG, "加载附近用户失败: " + t.getMessage());
|
||||||
// 取消超时回调
|
// 取消超时回调
|
||||||
if (nearbyLoadingTimeoutRunnable != null) {
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.ClipData;
|
import android.content.ClipData;
|
||||||
import android.content.ClipboardManager;
|
import android.content.ClipboardManager;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
|
@ -17,6 +18,7 @@ import android.widget.Toast;
|
||||||
import androidx.activity.result.ActivityResultLauncher;
|
import androidx.activity.result.ActivityResultLauncher;
|
||||||
import androidx.activity.result.contract.ActivityResultContracts;
|
import androidx.activity.result.contract.ActivityResultContracts;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.core.content.FileProvider;
|
import androidx.core.content.FileProvider;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.recyclerview.widget.GridLayoutManager;
|
import androidx.recyclerview.widget.GridLayoutManager;
|
||||||
|
|
@ -25,6 +27,7 @@ import com.bumptech.glide.Glide;
|
||||||
import com.example.livestreaming.BuildConfig;
|
import com.example.livestreaming.BuildConfig;
|
||||||
import com.example.livestreaming.databinding.ActivityProfileBinding;
|
import com.example.livestreaming.databinding.ActivityProfileBinding;
|
||||||
import com.example.livestreaming.ShareUtils;
|
import com.example.livestreaming.ShareUtils;
|
||||||
|
import com.example.livestreaming.location.TianDiTuLocationService;
|
||||||
import com.example.livestreaming.net.ApiClient;
|
import com.example.livestreaming.net.ApiClient;
|
||||||
import com.example.livestreaming.net.ApiResponse;
|
import com.example.livestreaming.net.ApiResponse;
|
||||||
import com.example.livestreaming.net.UserInfoResponse;
|
import com.example.livestreaming.net.UserInfoResponse;
|
||||||
|
|
@ -48,6 +51,8 @@ public class ProfileActivity extends AppCompatActivity {
|
||||||
|
|
||||||
private static final String TAG = "ProfileActivity";
|
private static final String TAG = "ProfileActivity";
|
||||||
private ActivityProfileBinding binding;
|
private ActivityProfileBinding binding;
|
||||||
|
private TianDiTuLocationService locationService;
|
||||||
|
private ActivityResultLauncher<String[]> requestLocationPermissionLauncher;
|
||||||
|
|
||||||
private static final String PREFS_NAME = "profile_prefs";
|
private static final String PREFS_NAME = "profile_prefs";
|
||||||
private static final String KEY_NAME = "profile_name";
|
private static final String KEY_NAME = "profile_name";
|
||||||
|
|
@ -76,6 +81,23 @@ public class ProfileActivity extends AppCompatActivity {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
binding = ActivityProfileBinding.inflate(getLayoutInflater());
|
binding = ActivityProfileBinding.inflate(getLayoutInflater());
|
||||||
setContentView(binding.getRoot());
|
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(
|
editProfileLauncher = registerForActivityResult(
|
||||||
|
|
@ -98,6 +120,9 @@ public class ProfileActivity extends AppCompatActivity {
|
||||||
setupNavigationClicks();
|
setupNavigationClicks();
|
||||||
setupWorksRecycler();
|
setupWorksRecycler();
|
||||||
setupProfileTabs();
|
setupProfileTabs();
|
||||||
|
|
||||||
|
// 自动获取用户位置并更新IP归属地
|
||||||
|
requestLocationAndUpdate();
|
||||||
|
|
||||||
BottomNavigationView bottomNavigation = binding.bottomNavInclude.bottomNavigation;
|
BottomNavigationView bottomNavigation = binding.bottomNavInclude.bottomNavigation;
|
||||||
bottomNavigation.setSelectedItemId(R.id.nav_profile);
|
bottomNavigation.setSelectedItemId(R.id.nav_profile);
|
||||||
|
|
@ -648,6 +673,9 @@ public class ProfileActivity extends AppCompatActivity {
|
||||||
UnreadMessageManager.updateBadge(bottomNav);
|
UnreadMessageManager.updateBadge(bottomNav);
|
||||||
// 检查主播状态并显示/隐藏主播中心按钮
|
// 检查主播状态并显示/隐藏主播中心按钮
|
||||||
checkAndUpdateStreamerButton();
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -626,14 +626,15 @@ public class SettingsPageActivity extends AppCompatActivity {
|
||||||
"• 麦克风:用于直播和语音聊天\n" +
|
"• 麦克风:用于直播和语音聊天\n" +
|
||||||
"• 位置:用于附近直播和发现功能\n" +
|
"• 位置:用于附近直播和发现功能\n" +
|
||||||
"• 存储:用于保存图片和视频\n\n" +
|
"• 存储:用于保存图片和视频\n\n" +
|
||||||
"点击确定将跳转到系统设置页面,您可以在那里管理应用权限。")
|
"点击确定将跳转到应用信息页面,请在页面中找到\"权限\"选项来管理各项权限。")
|
||||||
.setPositiveButton("前往设置", (d, w) -> {
|
.setPositiveButton("前往设置", (d, w) -> {
|
||||||
try {
|
try {
|
||||||
Intent intent = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
|
Intent intent = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
|
||||||
intent.setData(android.net.Uri.parse("package:" + getPackageName()));
|
intent.setData(android.net.Uri.parse("package:" + getPackageName()));
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Toast.makeText(this, "无法打开设置页面", Toast.LENGTH_SHORT).show();
|
Toast.makeText(this, "无法打开设置页面,请手动进入系统设置 > 应用管理", Toast.LENGTH_LONG).show();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.setNegativeButton("取消", null)
|
.setNegativeButton("取消", null)
|
||||||
|
|
|
||||||
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -43,6 +43,17 @@ public interface ApiService {
|
||||||
@POST("api/front/user/edit")
|
@POST("api/front/user/edit")
|
||||||
Call<ApiResponse<Object>> updateUserInfo(@Body UserEditRequest body);
|
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")
|
@GET("api/front/live/public/rooms")
|
||||||
|
|
@ -633,6 +644,13 @@ public interface ApiService {
|
||||||
@Query("radius") int radius,
|
@Query("radius") int radius,
|
||||||
@Query("limit") int limit);
|
@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")
|
@GET("api/front/fan-group/all")
|
||||||
Call<ApiResponse<List<Map<String, Object>>>> getAllMyFanGroups();
|
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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 匹配用户(心动信号)
|
* 匹配用户(心动信号)
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,12 @@ public class UserEditRequest {
|
||||||
@SerializedName("addres")
|
@SerializedName("addres")
|
||||||
private String addres; // 地址
|
private String addres; // 地址
|
||||||
|
|
||||||
|
@SerializedName("latitude")
|
||||||
|
private Double latitude; // 纬度
|
||||||
|
|
||||||
|
@SerializedName("longitude")
|
||||||
|
private Double longitude; // 经度
|
||||||
|
|
||||||
@SerializedName("mark")
|
@SerializedName("mark")
|
||||||
private String mark; // 个人签名/备注
|
private String mark; // 个人签名/备注
|
||||||
|
|
||||||
|
|
@ -34,6 +40,8 @@ public class UserEditRequest {
|
||||||
public void setBirthday(String birthday) { this.birthday = birthday; }
|
public void setBirthday(String birthday) { this.birthday = birthday; }
|
||||||
public void setSex(Integer sex) { this.sex = sex; }
|
public void setSex(Integer sex) { this.sex = sex; }
|
||||||
public void setAddres(String addres) { this.addres = addres; }
|
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 void setMark(String mark) { this.mark = mark; }
|
||||||
|
|
||||||
public String getNickname() { return nickname; }
|
public String getNickname() { return nickname; }
|
||||||
|
|
@ -41,5 +49,7 @@ public class UserEditRequest {
|
||||||
public String getBirthday() { return birthday; }
|
public String getBirthday() { return birthday; }
|
||||||
public Integer getSex() { return sex; }
|
public Integer getSex() { return sex; }
|
||||||
public String getAddres() { return addres; }
|
public String getAddres() { return addres; }
|
||||||
|
public Double getLatitude() { return latitude; }
|
||||||
|
public Double getLongitude() { return longitude; }
|
||||||
public String getMark() { return mark; }
|
public String getMark() { return mark; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -240,34 +240,6 @@
|
||||||
|
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
</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
|
<TextView
|
||||||
android:id="@+id/cancelButton"
|
android:id="@+id/cancelButton"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
|
|
@ -279,7 +251,7 @@
|
||||||
android:textColor="#111111"
|
android:textColor="#111111"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/locationLayout" />
|
app:layout_constraintTop_toBottomOf="@id/genderLayout" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,8 @@
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:paddingHorizontal="16dp"
|
android:paddingHorizontal="16dp"
|
||||||
android:paddingVertical="8dp">
|
android:paddingVertical="8dp"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:layout_width="20dp"
|
android:layout_width="20dp"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user