refactor: 拆分微信API到独立项目并迁移相关代码
1. 新增WeChatApi独立项目,将原WebApi中的微信相关控制器迁移至新项目 2. 新增后台权限管理相关实体、服务接口和基础控制器 3. 完善管理员用户服务,增加角色关联查询和赋值逻辑 4. 修复WebApi Swagger文档过滤微信API版本的问题 5. 更新解决方案文件,添加新的微信API项目引用 6. 新增微信API基础配置文件和项目属性配置
This commit is contained in:
@ -0,0 +1,52 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using QYZH.InteractiveMagazine.IService;
|
||||
using QYZH.InteractiveMagazine.Models.Dto;
|
||||
using QYZH.InteractiveMagazine.Models.Dto.Bag;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.WeChatApi.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// 小程序背包控制器
|
||||
/// </summary>
|
||||
public class BagController(IWxMallService mallService, ILogger<BagController> logger) : WeChatBaseController
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取背包物品列表
|
||||
/// </summary>
|
||||
/// <param name="itemType">物品类型筛选(可选): MakeUpCard, PetBg</param>
|
||||
[HttpGet("items")]
|
||||
public async Task<BaseResponse<List<UserBagOutput>>> GetBagItems([FromQuery] string? itemType = null)
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == 0) return Fail("未获取到用户信息") as dynamic;
|
||||
|
||||
var items = await mallService.GetBagItemsAsync(userId, itemType);
|
||||
return Success(items);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用背包物品(补签卡等消耗品)
|
||||
/// </summary>
|
||||
[HttpPost("useItem")]
|
||||
public async Task<BaseResponse<UseItemOutput>> UseItem([FromBody] UseItemInput input)
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == 0) return Fail("未获取到用户信息") as dynamic;
|
||||
|
||||
var result = await mallService.UseItemAsync(userId, input);
|
||||
return Success(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 宠物换肤
|
||||
/// </summary>
|
||||
[HttpPost("equipSkin")]
|
||||
public async Task<BaseResponse<object>> EquipSkin([FromBody] EquipSkinInput input)
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == 0) return Fail("未获取到用户信息") as dynamic;
|
||||
|
||||
await mallService.EquipSkinAsync(userId, input);
|
||||
return Success<object>(null!, input.SkinId == 0 ? "已恢复默认皮肤" : "换肤成功");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,144 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using QYZH.InteractiveMagazine.IService;
|
||||
using QYZH.InteractiveMagazine.Models.Common;
|
||||
using QYZH.InteractiveMagazine.Models.Dto;
|
||||
using QYZH.InteractiveMagazine.Models.Dto.CheckIn;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.WeChatApi.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// 签到控制器
|
||||
/// </summary>
|
||||
public class CheckInController : WeChatBaseController
|
||||
{
|
||||
private readonly ICheckInService _checkInService;
|
||||
private readonly ILogger<CheckInController> _logger;
|
||||
|
||||
public CheckInController(ICheckInService checkInService, ILogger<CheckInController> logger)
|
||||
{
|
||||
_checkInService = checkInService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用户签到
|
||||
/// </summary>
|
||||
/// <returns>签到结果(含奖励详情和余额)</returns>
|
||||
[HttpPost("checkIn")]
|
||||
public async Task<BaseResponse<CheckInOutput>> CheckInAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == 0)
|
||||
{
|
||||
return BaseResponse<CheckInOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||
}
|
||||
|
||||
var result = await _checkInService.CheckInAsync(userId);
|
||||
return Success(result, "签到成功");
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "签到业务异常: {Message}", ex.Message);
|
||||
return BaseResponse<CheckInOutput>.Fail(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "签到系统异常");
|
||||
return BaseResponse<CheckInOutput>.Fail("签到失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取签到信息(今日状态、连续天数、最近记录)
|
||||
/// </summary>
|
||||
/// <returns>签到信息</returns>
|
||||
[HttpGet("info")]
|
||||
public async Task<BaseResponse<CheckInInfoOutput>> GetCheckInInfoAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == 0)
|
||||
{
|
||||
return BaseResponse<CheckInInfoOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||
}
|
||||
|
||||
var result = await _checkInService.GetCheckInInfoAsync(userId);
|
||||
return Success(result);
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "获取签到信息业务异常: {Message}", ex.Message);
|
||||
return BaseResponse<CheckInInfoOutput>.Fail(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "获取签到信息系统异常");
|
||||
return BaseResponse<CheckInInfoOutput>.Fail("获取签到信息失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 补签(消耗补签卡,补签历史漏签日期)
|
||||
/// </summary>
|
||||
/// <param name="input">补签输入(目标日期)</param>
|
||||
/// <returns>补签结果(含奖励详情和余额)</returns>
|
||||
[HttpPost("makeUp")]
|
||||
public async Task<BaseResponse<CheckInOutput>> MakeUpCheckInAsync([FromBody] MakeUpCheckInInput input)
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == 0)
|
||||
{
|
||||
return BaseResponse<CheckInOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||
}
|
||||
|
||||
var result = await _checkInService.MakeUpCheckInAsync(userId, input.TargetDate);
|
||||
return Success(result, "补签成功");
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "补签业务异常: {Message}", ex.Message);
|
||||
return BaseResponse<CheckInOutput>.Fail(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "补签系统异常");
|
||||
return BaseResponse<CheckInOutput>.Fail("补签失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取可补签的日期列表(历史漏签日期)
|
||||
/// </summary>
|
||||
/// <param name="days">往前查看天数(默认30天)</param>
|
||||
/// <returns>漏签日期列表</returns>
|
||||
[HttpGet("missedDates")]
|
||||
public async Task<BaseResponse<List<DateTime>>> GetMissedDatesAsync([FromQuery] int days = 30)
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == 0)
|
||||
{
|
||||
return BaseResponse<List<DateTime>>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||
}
|
||||
|
||||
var result = await _checkInService.GetMissedDatesAsync(userId, days);
|
||||
return Success(result);
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "获取可补签日期业务异常: {Message}", ex.Message);
|
||||
return BaseResponse<List<DateTime>>.Fail(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "获取可补签日期系统异常");
|
||||
return BaseResponse<List<DateTime>>.Fail("获取可补签日期失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,129 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using QYZH.InteractiveMagazine.IService;
|
||||
using QYZH.InteractiveMagazine.Models.Common;
|
||||
using QYZH.InteractiveMagazine.Models.Dto;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.WeChatApi.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// 小程序社区控制器
|
||||
/// </summary>
|
||||
public class CommunityController(IWeChatCommunityService communityService, ILogger<CommunityController> logger) : WeChatBaseController
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取社区Feed流(翻页)
|
||||
/// </summary>
|
||||
/// <param name="cursor">游标(上一页最后一条消息ID,首次不传)</param>
|
||||
[HttpGet("feed")]
|
||||
public async Task<BaseResponse<WxFeedOutput>> GetFeed([FromQuery] long? cursor = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == 0)
|
||||
{
|
||||
return BaseResponse<WxFeedOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||
}
|
||||
|
||||
var result = await communityService.GetFeedAsync(userId, cursor);
|
||||
return Success(result);
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
{
|
||||
logger.LogWarning(ex, "获取社区Feed业务异常: {Message}", ex.Message);
|
||||
return BaseResponse<WxFeedOutput>.Fail(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "获取社区Feed系统异常");
|
||||
return BaseResponse<WxFeedOutput>.Fail("获取社区内容失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 下拉刷新社区Feed
|
||||
/// </summary>
|
||||
[HttpGet("refresh")]
|
||||
public async Task<BaseResponse<WxFeedOutput>> RefreshFeed()
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == 0)
|
||||
{
|
||||
return BaseResponse<WxFeedOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||
}
|
||||
|
||||
var result = await communityService.RefreshFeedAsync(userId);
|
||||
return Success(result);
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
{
|
||||
logger.LogWarning(ex, "刷新社区Feed业务异常: {Message}", ex.Message);
|
||||
return BaseResponse<WxFeedOutput>.Fail(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "刷新社区Feed系统异常");
|
||||
return BaseResponse<WxFeedOutput>.Fail("刷新社区内容失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 点赞
|
||||
/// </summary>
|
||||
[HttpPost("like")]
|
||||
public async Task<BaseResponse<WxLikeOutput>> Like([FromBody] WxLikeInput input)
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == 0)
|
||||
{
|
||||
return BaseResponse<WxLikeOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||
}
|
||||
|
||||
var result = await communityService.LikeAsync(userId, input);
|
||||
return Success(result, "点赞成功");
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
{
|
||||
logger.LogWarning(ex, "点赞业务异常: {Message}", ex.Message);
|
||||
return BaseResponse<WxLikeOutput>.Fail(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "点赞系统异常,参数:{Input}", input);
|
||||
return BaseResponse<WxLikeOutput>.Fail("点赞失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取消点赞
|
||||
/// </summary>
|
||||
[HttpPost("unlike")]
|
||||
public async Task<BaseResponse<WxLikeOutput>> Unlike([FromBody] WxLikeInput input)
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == 0)
|
||||
{
|
||||
return BaseResponse<WxLikeOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||
}
|
||||
|
||||
var result = await communityService.UnlikeAsync(userId, input.MessageId);
|
||||
return Success(result, "已取消点赞");
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
{
|
||||
logger.LogWarning(ex, "取消点赞业务异常: {Message}", ex.Message);
|
||||
return BaseResponse<WxLikeOutput>.Fail(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "取消点赞系统异常,参数:{Input}", input);
|
||||
return BaseResponse<WxLikeOutput>.Fail("取消点赞失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,90 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using QYZH.InteractiveMagazine.IService;
|
||||
using QYZH.InteractiveMagazine.Models.Common;
|
||||
using QYZH.InteractiveMagazine.Models.Dto;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.WeChatApi.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// 小程序期刊管理控制器
|
||||
/// </summary>
|
||||
public class JournalController : WeChatBaseController
|
||||
{
|
||||
private readonly IUserJournalService _userJournalService;
|
||||
private readonly ILogger<JournalController> _logger;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化小程序期刊控制器
|
||||
/// </summary>
|
||||
/// <param name="userJournalService">用户期刊关联服务</param>
|
||||
/// <param name="logger">日志服务</param>
|
||||
public JournalController(
|
||||
IUserJournalService userJournalService,
|
||||
ILogger<JournalController> logger)
|
||||
{
|
||||
_userJournalService = userJournalService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用户扫码绑定期刊
|
||||
/// </summary>
|
||||
/// <param name="input">绑定输入(JournalId、JournalInstanceId 从扫码内容解析,Type 默认 Subscribe)</param>
|
||||
/// <returns>绑定结果</returns>
|
||||
[HttpPost("bind")]
|
||||
public async Task<BaseResponse<BindJournalOutput>> BindAsync([FromBody] BindJournalInput input)
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == 0)
|
||||
{
|
||||
return BaseResponse<BindJournalOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||
}
|
||||
|
||||
var result = await _userJournalService.BindJournalAsync(userId, input);
|
||||
return Success(result, "绑定期刊成功");
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "绑定期刊业务异常: {Message}", ex.Message);
|
||||
return BaseResponse<BindJournalOutput>.Fail(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "绑定期刊系统异常,参数:{Input}", input);
|
||||
return BaseResponse<BindJournalOutput>.Fail("绑定期刊失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前用户的期刊绑定列表
|
||||
/// </summary>
|
||||
/// <param name="input">查询条件(期刊Id、实例Id、关联类型)</param>
|
||||
/// <returns>分页结果</returns>
|
||||
[HttpPost("list")]
|
||||
public async Task<BaseResponse<PageListModel<BindJournalOutput>>> GetListAsync([FromBody] UserJournalQueryInput input)
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == 0)
|
||||
{
|
||||
return BaseResponse<PageListModel<BindJournalOutput>>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||
}
|
||||
|
||||
var result = await _userJournalService.GetUserJournalsAsync(userId, input);
|
||||
return Success(result);
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "查询期刊绑定列表业务异常: {Message}", ex.Message);
|
||||
return BaseResponse<PageListModel<BindJournalOutput>>.Fail(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "查询期刊绑定列表系统异常");
|
||||
return BaseResponse<PageListModel<BindJournalOutput>>.Fail("查询期刊绑定列表失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,70 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using QYZH.InteractiveMagazine.IService;
|
||||
using QYZH.InteractiveMagazine.Models.Dto;
|
||||
using QYZH.InteractiveMagazine.Models.Dto.Mall;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.WeChatApi.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// 小程序商城控制器
|
||||
/// </summary>
|
||||
public class MallController(IWxMallService mallService, ILogger<MallController> logger) : WeChatBaseController
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取商城商品列表
|
||||
/// </summary>
|
||||
/// <param name="type">商品类型筛选(可选): MakeUpCard, PetBg</param>
|
||||
[HttpGet("products")]
|
||||
public async Task<BaseResponse<List<WxProductOutput>>> GetProducts([FromQuery] string? type = null)
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == 0) return Fail("未获取到用户信息") as dynamic;
|
||||
|
||||
var products = await mallService.GetProductsAsync(userId, type);
|
||||
return Success(products);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取商品详情
|
||||
/// </summary>
|
||||
/// <param name="id">商品Id</param>
|
||||
[HttpGet("product/{id}")]
|
||||
public async Task<BaseResponse<WxProductOutput>> GetProductDetail(long id)
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == 0) return Fail("未获取到用户信息") as dynamic;
|
||||
|
||||
var product = await mallService.GetProductDetailAsync(userId, id);
|
||||
if (product == null)
|
||||
return BaseResponse<WxProductOutput>.Fail(ResultCode.DENY, "商品不存在或已下架");
|
||||
|
||||
return Success(product);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 积分兑换商品
|
||||
/// </summary>
|
||||
[HttpPost("exchange")]
|
||||
public async Task<BaseResponse<ExchangeOutput>> Exchange([FromBody] ExchangeInput input)
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == 0) return Fail("未获取到用户信息") as dynamic;
|
||||
|
||||
var result = await mallService.ExchangeAsync(userId, input);
|
||||
return Success(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取我的兑换记录
|
||||
/// </summary>
|
||||
/// <param name="limit">返回数量(默认20)</param>
|
||||
[HttpGet("exchangeRecords")]
|
||||
public async Task<BaseResponse<List<ExchangeRecordOutput>>> GetExchangeRecords([FromQuery] int limit = 20)
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == 0) return Fail("未获取到用户信息") as dynamic;
|
||||
|
||||
var records = await mallService.GetExchangeRecordsAsync(userId, limit);
|
||||
return Success(records);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,99 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using QYZH.InteractiveMagazine.IService;
|
||||
using QYZH.InteractiveMagazine.Models.Common;
|
||||
using QYZH.InteractiveMagazine.Models.Dto;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.WeChatApi.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// 小程序勋章控制器
|
||||
/// </summary>
|
||||
public class MedalController(IMedalService medalService, ILogger<MedalController> logger) : WeChatBaseController
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取所有勋章列表(含当前用户拥有状态)
|
||||
/// </summary>
|
||||
[HttpGet("all")]
|
||||
public async Task<BaseResponse<List<WxMedalListOutput>>> GetAllMedals()
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == 0)
|
||||
{
|
||||
return BaseResponse<List<WxMedalListOutput>>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||
}
|
||||
|
||||
var result = await medalService.GetAllMedalsAsync(userId);
|
||||
return Success(result);
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
{
|
||||
logger.LogWarning(ex, "获取勋章列表业务异常: {Message}", ex.Message);
|
||||
return BaseResponse<List<WxMedalListOutput>>.Fail(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "获取勋章列表系统异常");
|
||||
return BaseResponse<List<WxMedalListOutput>>.Fail("获取勋章列表失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取用户已拥有的勋章列表
|
||||
/// </summary>
|
||||
[HttpGet("my")]
|
||||
public async Task<BaseResponse<List<WxUserMedalOutput>>> GetUserMedals()
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == 0)
|
||||
{
|
||||
return BaseResponse<List<WxUserMedalOutput>>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||
}
|
||||
|
||||
var result = await medalService.GetUserMedalsAsync(userId);
|
||||
return Success(result);
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
{
|
||||
logger.LogWarning(ex, "获取用户勋章列表业务异常: {Message}", ex.Message);
|
||||
return BaseResponse<List<WxUserMedalOutput>>.Fail(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "获取用户勋章列表系统异常");
|
||||
return BaseResponse<List<WxUserMedalOutput>>.Fail("获取用户勋章列表失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 激活/获得勋章
|
||||
/// </summary>
|
||||
[HttpPost("activate")]
|
||||
public async Task<BaseResponse<object>> ActivateMedal([FromBody] WxMedalActivateInput input)
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == 0)
|
||||
{
|
||||
return BaseResponse<object>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||
}
|
||||
|
||||
await medalService.ActivateMedalAsync(userId, input);
|
||||
return Success<object>(null!, "勋章激活成功");
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
{
|
||||
logger.LogWarning(ex, "激活勋章业务异常: {Message}", ex.Message);
|
||||
return BaseResponse<object>.Fail(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "激活勋章系统异常,参数:{Input}", input);
|
||||
return BaseResponse<object>.Fail("激活勋章失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
}
|
||||
114
QYZH.InteractiveMagazine.WeChatApi/Controllers/PetController.cs
Normal file
114
QYZH.InteractiveMagazine.WeChatApi/Controllers/PetController.cs
Normal file
@ -0,0 +1,114 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using QYZH.InteractiveMagazine.IService;
|
||||
using QYZH.InteractiveMagazine.Models.Common;
|
||||
using QYZH.InteractiveMagazine.Models.Dto;
|
||||
using QYZH.InteractiveMagazine.Models.Dto.Pet;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.WeChatApi.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// 小程序宠物管理控制器
|
||||
/// </summary>
|
||||
public class PetController : WeChatBaseController
|
||||
{
|
||||
private readonly IPetService _petService;
|
||||
private readonly ILogger<PetController> _logger;
|
||||
|
||||
public PetController(IPetService petService, ILogger<PetController> logger)
|
||||
{
|
||||
_petService = petService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前用户的宠物信息
|
||||
/// </summary>
|
||||
[HttpGet("mine")]
|
||||
public async Task<BaseResponse<PetOutput>> GetMyPetAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == 0)
|
||||
{
|
||||
return BaseResponse<PetOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||
}
|
||||
|
||||
var pet = await _petService.GetPetByUserIdAsync(userId);
|
||||
if (pet == null)
|
||||
{
|
||||
return BaseResponse<PetOutput>.Fail("未找到宠物信息");
|
||||
}
|
||||
|
||||
return Success(pet);
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "获取宠物信息业务异常: {Message}", ex.Message);
|
||||
return BaseResponse<PetOutput>.Fail(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "获取宠物信息系统异常");
|
||||
return BaseResponse<PetOutput>.Fail("获取宠物信息失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 喂养宠物(增加成长值,触发进化检查)
|
||||
/// </summary>
|
||||
[HttpPost("feed")]
|
||||
public async Task<BaseResponse<FeedPetOutput>> FeedPetAsync([FromBody] FeedPetInput input)
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == 0)
|
||||
{
|
||||
return BaseResponse<FeedPetOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||
}
|
||||
|
||||
var result = await _petService.FeedPetAsync(userId, input);
|
||||
return Success(result, "喂养成功");
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "喂养宠物业务异常: {Message}", ex.Message);
|
||||
return BaseResponse<FeedPetOutput>.Fail(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "喂养宠物系统异常,参数:{@Input}", input);
|
||||
return BaseResponse<FeedPetOutput>.Fail("喂养宠物失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取宠物喂养记录列表
|
||||
/// </summary>
|
||||
[HttpGet("records/{petId}")]
|
||||
public async Task<BaseResponse<PageListModel<FeedingRecordOutput>>> GetFeedingRecordsAsync(long petId, [FromQuery] PageQueryModel pageQuery)
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == 0)
|
||||
{
|
||||
return BaseResponse<PageListModel<FeedingRecordOutput>>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||
}
|
||||
|
||||
var result = await _petService.GetFeedingRecordsAsync(userId, petId, pageQuery);
|
||||
return Success(result);
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "查询喂养记录业务异常: {Message}", ex.Message);
|
||||
return BaseResponse<PageListModel<FeedingRecordOutput>>.Fail(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "查询喂养记录系统异常");
|
||||
return BaseResponse<PageListModel<FeedingRecordOutput>>.Fail("查询喂养记录失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,268 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using QYZH.InteractiveMagazine.IService;
|
||||
using QYZH.InteractiveMagazine.Models.Common;
|
||||
using QYZH.InteractiveMagazine.Models.Dto;
|
||||
using QYZH.InteractiveMagazine.Models.Dto.UserAnswerTaskService;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.WeChatApi.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// 小程序首页控制器
|
||||
/// </summary>
|
||||
public class UserAnswerTaskController : WeChatBaseController
|
||||
{
|
||||
private readonly IUserAnswerTaskService _userAnswerTaskService;
|
||||
private readonly ILogger<UserAnswerTaskController> _logger;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
public UserAnswerTaskController(
|
||||
IUserAnswerTaskService userAnswerTaskService,
|
||||
ILogger<UserAnswerTaskController> logger)
|
||||
{
|
||||
_userAnswerTaskService = userAnswerTaskService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
private const long ConstUserId = 821752253853766;
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前用户的期刊学习进度列表
|
||||
/// </summary>
|
||||
/// <returns>按期刊分组的学习进度数据</returns>
|
||||
[HttpGet("progress")]
|
||||
public async Task<BaseResponse<List<JournalProgressOutput>>> GetProgressAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == 0)
|
||||
{
|
||||
return BaseResponse<List<JournalProgressOutput>>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||
}
|
||||
|
||||
var result = await _userAnswerTaskService.GetJournalProgressAsync(userId);
|
||||
return Success(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "获取学习进度失败,UserId: {UserId}", GetCurrentUserId());
|
||||
return BaseResponse<List<JournalProgressOutput>>.Fail("获取学习进度失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 获取目录列表(按一级目录分组,二级目录含完成状态)
|
||||
/// </summary>
|
||||
/// <param name="journalId">期刊Id</param>
|
||||
/// <returns>按一级目录分组的目录列表</returns>
|
||||
[HttpGet("catalog-list/{journalId:long}")]
|
||||
public async Task<BaseResponse<List<CatalogListOutput>>> GetCatalogListAsync([Required(ErrorMessage = "参数错误")] long journalId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == 0)
|
||||
{
|
||||
return BaseResponse<List<CatalogListOutput>>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||
}
|
||||
|
||||
if (journalId <= 0)
|
||||
{
|
||||
return BaseResponse<List<CatalogListOutput>>.Fail(ResultCode.PARAM_ERROR, "期刊Id不能为空");
|
||||
}
|
||||
|
||||
var result = await _userAnswerTaskService.GetCatalogListAsync(journalId, userId);
|
||||
return Success(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "获取目录列表失败,JournalId: {JournalId}, UserId: {UserId}", journalId, GetCurrentUserId());
|
||||
return BaseResponse<List<CatalogListOutput>>.Fail("获取目录列表失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取一级目录概览(含二级目录详情、页码范围、任务完成状态)
|
||||
/// </summary>
|
||||
/// <param name="catalogId">一级目录Id</param>
|
||||
/// <returns>一级目录概览数据</returns>
|
||||
[HttpGet("catalog-overview/{catalogId:long}")]
|
||||
public async Task<BaseResponse<CatalogOverviewOutput>> GetCatalogOverviewAsync([Required(ErrorMessage = "参数错误")] long catalogId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == 0)
|
||||
{
|
||||
return BaseResponse<CatalogOverviewOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||
}
|
||||
|
||||
if (catalogId <= 0)
|
||||
{
|
||||
return BaseResponse<CatalogOverviewOutput>.Fail(ResultCode.PARAM_ERROR, "目录Id不能为空");
|
||||
}
|
||||
|
||||
var result = await _userAnswerTaskService.GetCatalogOverviewAsync(catalogId, userId);
|
||||
if (result == null)
|
||||
{
|
||||
return BaseResponse<CatalogOverviewOutput>.Fail("未找到该目录");
|
||||
}
|
||||
|
||||
return Success(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "获取目录概览失败,CatalogId: {CatalogId}, UserId: {UserId}", catalogId, GetCurrentUserId());
|
||||
return BaseResponse<CatalogOverviewOutput>.Fail("获取目录概览失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取用户答题任务列表
|
||||
/// </summary>
|
||||
/// <param name="catalogId">二级目录Id</param>
|
||||
/// <returns>答题任务列表</returns>
|
||||
[HttpGet("task-list/{catalogId:long}")]
|
||||
public async Task<BaseResponse<UserAnswerTaskListOutput>> GetTaskListAsync([Required(ErrorMessage = "参数错误")] long catalogId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == 0)
|
||||
{
|
||||
return BaseResponse<UserAnswerTaskListOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||
}
|
||||
|
||||
if (catalogId <= 0)
|
||||
{
|
||||
return BaseResponse<UserAnswerTaskListOutput>.Fail(ResultCode.PARAM_ERROR, "目录Id不能为空");
|
||||
}
|
||||
|
||||
var result = await _userAnswerTaskService.GetUserAnswerTaskListAsync(catalogId, userId);
|
||||
if (result == null)
|
||||
{
|
||||
return BaseResponse<UserAnswerTaskListOutput>.Fail("未找到该目录");
|
||||
}
|
||||
|
||||
return Success(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "获取任务列表失败,CatalogId: {CatalogId}, UserId: {UserId}", catalogId, GetCurrentUserId());
|
||||
return BaseResponse<UserAnswerTaskListOutput>.Fail("获取任务列表失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取跨页题目详情
|
||||
/// </summary>
|
||||
/// <param name="groupId">任务分组ID</param>
|
||||
/// <returns>跨页题目详情</returns>
|
||||
[HttpGet("cross-page-task/{groupId:long}")]
|
||||
public async Task<BaseResponse<CrossPageTaskOutput>> GetCrossPageTaskAsync([Required(ErrorMessage ="参数错误")] long groupId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == 0)
|
||||
{
|
||||
return BaseResponse<CrossPageTaskOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||
}
|
||||
|
||||
if (groupId <= 0)
|
||||
{
|
||||
return BaseResponse<CrossPageTaskOutput>.Fail(ResultCode.PARAM_ERROR, "任务Id不能为空");
|
||||
}
|
||||
|
||||
var result = await _userAnswerTaskService.GetCrossPageTaskAsync(groupId, userId);
|
||||
if (result == null)
|
||||
{
|
||||
return BaseResponse<CrossPageTaskOutput>.Fail("未找到该任务");
|
||||
}
|
||||
|
||||
return Success(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "获取跨页题目失败,JournalPageTaskGroupId: {groupId}, UserId: {UserId}", groupId, GetCurrentUserId());
|
||||
return BaseResponse<CrossPageTaskOutput>.Fail("获取跨页题目失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 领取任务积分
|
||||
/// </summary>
|
||||
/// <param name="groupId">任务分组Id(GroupId)</param>
|
||||
/// <returns>领取结果</returns>
|
||||
[HttpPost("claim-points/{groupId:long}")]
|
||||
public async Task<BaseResponse<ClaimTaskPointsOutput>> ClaimTaskPointsAsync([Required(ErrorMessage = "参数错误")] long groupId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == 0)
|
||||
{
|
||||
return BaseResponse<ClaimTaskPointsOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||
}
|
||||
|
||||
if (groupId <= 0)
|
||||
{
|
||||
return BaseResponse<ClaimTaskPointsOutput>.Fail(ResultCode.PARAM_ERROR, "任务分组Id不能为空");
|
||||
}
|
||||
|
||||
var result = await _userAnswerTaskService.ClaimTaskPointsAsync(groupId, userId);
|
||||
return Success(result);
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "领取任务积分业务异常,groupId: {groupId}, UserId: {UserId}", groupId, GetCurrentUserId());
|
||||
return BaseResponse<ClaimTaskPointsOutput>.Fail(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "领取任务积分失败,groupId: {groupId}, UserId: {UserId}", groupId, GetCurrentUserId());
|
||||
return BaseResponse<ClaimTaskPointsOutput>.Fail("领取任务积分失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量领取任务积分(一键领取)
|
||||
/// </summary>
|
||||
/// <param name="input">批量领取参数</param>
|
||||
/// <returns>批量领取结果</returns>
|
||||
[HttpPost("batch-claim-points")]
|
||||
public async Task<BaseResponse<BatchClaimPointsOutput>> BatchClaimPointsAsync([FromBody] BatchClaimPointsInput input)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == 0)
|
||||
{
|
||||
return BaseResponse<BatchClaimPointsOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||
}
|
||||
|
||||
if (input == null || input.GroupIds == null || input.GroupIds.Count == 0)
|
||||
{
|
||||
return BaseResponse<BatchClaimPointsOutput>.Fail(ResultCode.PARAM_ERROR, "任务分组Id列表不能为空");
|
||||
}
|
||||
|
||||
var result = await _userAnswerTaskService.BatchClaimPointsAsync(input, userId);
|
||||
return Success(result);
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "批量领取积分业务异常,UserId: {UserId}", GetCurrentUserId());
|
||||
return BaseResponse<BatchClaimPointsOutput>.Fail(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "批量领取积分失败,UserId: {UserId}", GetCurrentUserId());
|
||||
return BaseResponse<BatchClaimPointsOutput>.Fail("批量领取积分失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,185 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using QYZH.InteractiveMagazine.IService;
|
||||
|
||||
using QYZH.InteractiveMagazine.Models.Common;
|
||||
using QYZH.InteractiveMagazine.Models.Dto;
|
||||
using QYZH.InteractiveMagazine.Models.WeChat;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.WeChatApi.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// 微信小程序认证控制器
|
||||
/// </summary>
|
||||
public class WeChatAuthController : WeChatBaseController
|
||||
{
|
||||
private readonly IWeChatAuthService _weChatAuthService;
|
||||
private readonly ILogger<WeChatAuthController> _logger;
|
||||
|
||||
public WeChatAuthController(IWeChatAuthService weChatAuthService, ILogger<WeChatAuthController> logger)
|
||||
{
|
||||
_weChatAuthService = weChatAuthService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 微信小程序登录(首次仅创建 WxUser,不自动创建 User)
|
||||
/// </summary>
|
||||
/// <param name="input">登录输入(含微信 code 和可选的手机号 code)</param>
|
||||
/// <returns>登录结果(含 Token 和用户列表)</returns>
|
||||
[AllowAnonymous]
|
||||
[HttpPost("login")]
|
||||
public async Task<BaseResponse<WeChatLoginOutput>> LoginAsync([FromBody] WeChatLoginInput input)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _weChatAuthService.LoginAsync(input);
|
||||
return Success(result);
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "微信登录业务异常: {Message}", ex.Message);
|
||||
return BaseResponse<WeChatLoginOutput>.Fail(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "微信登录系统异常");
|
||||
return BaseResponse<WeChatLoginOutput>.Fail("微信登录失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 微信小程序快捷登录(通过 OpenId 直接登录,用户需已存在)
|
||||
/// </summary>
|
||||
/// <param name="input">快捷登录输入(含 OpenId)</param>
|
||||
/// <returns>登录结果(含 Token 和用户列表)</returns>
|
||||
[AllowAnonymous]
|
||||
[HttpPost("quickLogin")]
|
||||
public async Task<BaseResponse<WeChatLoginOutput>> QuickLoginAsync([FromBody] WeChatQuickLoginInput input)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _weChatAuthService.QuickLoginAsync(input);
|
||||
return Success(result);
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "微信快捷登录业务异常: {Message}", ex.Message);
|
||||
return BaseResponse<WeChatLoginOutput>.Fail(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "微信快捷登录系统异常");
|
||||
return BaseResponse<WeChatLoginOutput>.Fail("快捷登录失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 切换用户(同一 WxUser 下切换 User 身份)
|
||||
/// </summary>
|
||||
[HttpPost("switchUser")]
|
||||
public async Task<BaseResponse<WeChatSwitchUserOutput>> SwitchUserAsync([FromBody] WeChatSwitchUserInput input)
|
||||
{
|
||||
try
|
||||
{
|
||||
var wxUserId = GetCurrentWxUserId();
|
||||
if (wxUserId == null)
|
||||
return BaseResponse<WeChatSwitchUserOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||
|
||||
var currentUserId = GetCurrentUserId();
|
||||
var result = await _weChatAuthService.SwitchUserAsync(wxUserId.Value, currentUserId, input);
|
||||
return Success(result);
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "切换用户业务异常: {Message}", ex.Message);
|
||||
return BaseResponse<WeChatSwitchUserOutput>.Fail(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "切换用户系统异常");
|
||||
return BaseResponse<WeChatSwitchUserOutput>.Fail("切换用户失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前微信用户下的所有用户列表
|
||||
/// </summary>
|
||||
[HttpGet("users")]
|
||||
public async Task<BaseResponse<List<WxUserOutput>>> GetUsersAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var wxUserId = GetCurrentWxUserId();
|
||||
if (wxUserId == null)
|
||||
return BaseResponse<List<WxUserOutput>>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||
|
||||
var result = await _weChatAuthService.GetUsersAsync(wxUserId.Value);
|
||||
return Success(result);
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "获取用户列表业务异常: {Message}", ex.Message);
|
||||
return BaseResponse<List<WxUserOutput>>.Fail(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "获取用户列表系统异常");
|
||||
return BaseResponse<List<WxUserOutput>>.Fail("获取用户列表失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在当前微信用户下新增用户(子用户/角色)
|
||||
/// </summary>
|
||||
[HttpPost("createUser")]
|
||||
public async Task<BaseResponse<WxUserOutput>> CreateUserAsync([FromBody] CreateChildUserInput input)
|
||||
{
|
||||
try
|
||||
{
|
||||
var wxUserId = GetCurrentWxUserId();
|
||||
if (wxUserId == null)
|
||||
return BaseResponse<WxUserOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||
|
||||
var result = await _weChatAuthService.CreateUserAsync(wxUserId.Value, input);
|
||||
return Success(result);
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "新增用户业务异常: {Message}", ex.Message);
|
||||
return BaseResponse<WxUserOutput>.Fail(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "新增用户系统异常");
|
||||
return BaseResponse<WxUserOutput>.Fail("新增用户失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 修改家长名字(WxUser.Name)
|
||||
/// </summary>
|
||||
[HttpPost("updateName")]
|
||||
public async Task<BaseResponse<WxUserInfoOutput>> UpdateWxUserNameAsync([FromBody] UpdateWxUserNameInput input)
|
||||
{
|
||||
try
|
||||
{
|
||||
var wxUserId = GetCurrentWxUserId();
|
||||
if (wxUserId == null)
|
||||
return BaseResponse<WxUserInfoOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||
|
||||
var result = await _weChatAuthService.UpdateWxUserNameAsync(wxUserId.Value, input);
|
||||
return Success(result, "修改成功");
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "修改家长名字业务异常: {Message}", ex.Message);
|
||||
return BaseResponse<WxUserInfoOutput>.Fail(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "修改家长名字系统异常");
|
||||
return BaseResponse<WxUserInfoOutput>.Fail("修改家长名字失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,78 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using QYZH.InteractiveMagazine.Infrastructure.Auth;
|
||||
using QYZH.InteractiveMagazine.Models.Dto;
|
||||
using QYZH.InteractiveMagazine.Models.Enum;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.WeChatApi.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// 小程序基础控制器
|
||||
/// </summary>
|
||||
[Authorize]
|
||||
[ApiController]
|
||||
[Route("wechat/api/[controller]")]
|
||||
[ApiExplorerSettings(GroupName = nameof(ApiVersionEnum.Wechat))]
|
||||
public abstract class WeChatBaseController : ControllerBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取当前激活用户ID(Users.Id,来自 JWT NameIdentifier)
|
||||
/// </summary>
|
||||
/// <returns>User ID(无激活用户时为 0)</returns>
|
||||
protected long GetCurrentUserId()
|
||||
{
|
||||
var userIdClaim = User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier);
|
||||
if (userIdClaim != null && long.TryParse(userIdClaim.Value, out var userId))
|
||||
{
|
||||
return userId;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前微信用户ID(WxUser.Id,来自 JWT WxUserId claim)
|
||||
/// </summary>
|
||||
/// <returns>WxUser ID</returns>
|
||||
protected long? GetCurrentWxUserId()
|
||||
{
|
||||
var wxUserIdClaim = User.Claims.FirstOrDefault(c => c.Type == JwtHelper.WxUserIdClaimType);
|
||||
if (wxUserIdClaim != null && long.TryParse(wxUserIdClaim.Value, out var wxUserId))
|
||||
{
|
||||
return wxUserId;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前用户名
|
||||
/// </summary>
|
||||
/// <returns>用户名</returns>
|
||||
protected string? GetCurrentUserName()
|
||||
{
|
||||
return User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Name)?.Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 成功响应
|
||||
/// </summary>
|
||||
/// <typeparam name="T">数据类型</typeparam>
|
||||
/// <param name="data">数据</param>
|
||||
/// <param name="message">提示信息</param>
|
||||
/// <returns>统一响应对象</returns>
|
||||
protected BaseResponse<T> Success<T>(T data, string message = "操作成功")
|
||||
{
|
||||
return BaseResponse<T>.Success(data, message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 失败响应
|
||||
/// </summary>
|
||||
/// <param name="message">提示信息</param>
|
||||
/// <param name="code">状态码</param>
|
||||
/// <returns>统一响应对象</returns>
|
||||
protected BaseResponse<object> Fail(string message, int code = 500)
|
||||
{
|
||||
return BaseResponse<object>.Fail(ResultCode.GLOBAL_ERROR, message);
|
||||
}
|
||||
}
|
||||
189
QYZH.InteractiveMagazine.WeChatApi/Program.cs
Normal file
189
QYZH.InteractiveMagazine.WeChatApi/Program.cs
Normal file
@ -0,0 +1,189 @@
|
||||
using Microsoft.AspNetCore.DataProtection;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.ResponseCompression;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using QYZH.InteractiveMagazine.Common.Extensions;
|
||||
using QYZH.InteractiveMagazine.Common.Helpers;
|
||||
using QYZH.InteractiveMagazine.Infrastructure.Autofacs;
|
||||
using QYZH.InteractiveMagazine.Infrastructure.Context;
|
||||
using QYZH.InteractiveMagazine.Infrastructure.Extensions;
|
||||
using QYZH.InteractiveMagazine.Infrastructure.Middleware;
|
||||
using QYZH.InteractiveMagazine.Infrastructure.RabbitMQ;
|
||||
using QYZH.InteractiveMagazine.Infrastructure.Redis;
|
||||
using QYZH.InteractiveMagazine.Infrastructure.SDK;
|
||||
using QYZH.InteractiveMagazine.Models.Enum;
|
||||
using QYZH.InteractiveMagazine.Models.Settings;
|
||||
using QYZH.InteractiveMagazine.Repository;
|
||||
using QYZH.InteractiveMagazine.Repository.Core;
|
||||
using Serilog;
|
||||
using SqlSugar.IOC;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
using Swashbuckle.AspNetCore.SwaggerUI;
|
||||
using System.Text.Json.Serialization;
|
||||
using Yitter.IdGenerator;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
builder.Configuration.AddJsonFile("medal-rule-config.json", optional: false, reloadOnChange: true);
|
||||
|
||||
YitIdHelper.SetIdGenerator(new IdGeneratorOptions { WorkerId = 1 });
|
||||
|
||||
builder.UseAutofac();
|
||||
|
||||
builder.InitSqlSugarDb(new IocConfig
|
||||
{
|
||||
ConfigId = 0,
|
||||
DbType = IocDbType.MySql,
|
||||
ConnectionString = builder.Configuration.GetConnectionString("DefaultConnection"),
|
||||
IsAutoCloseConnection = true,
|
||||
});
|
||||
|
||||
builder.Services.AddDataProtection()
|
||||
.PersistKeysToFileSystem(new DirectoryInfo(Path.Combine(Directory.GetCurrentDirectory(), "DataProtection")));
|
||||
|
||||
builder.Services.AddCSRedisCacheExtension(builder.Configuration.GetSection("RedisSettings"));
|
||||
builder.Services.AddRabbitMQ(builder.Configuration);
|
||||
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.ReadFrom.Configuration(builder.Configuration)
|
||||
.Enrich.FromLogContext()
|
||||
.CreateLogger();
|
||||
|
||||
builder.Host.UseSerilog();
|
||||
|
||||
builder.Services.AddControllers(options =>
|
||||
{
|
||||
options.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true;
|
||||
options.Filters.Add<ModelValidActionFilterAttribute>();
|
||||
})
|
||||
.AddJsonOptions(options =>
|
||||
{
|
||||
options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
|
||||
options.JsonSerializerOptions.Converters.Add(new JsonConverterUtil.DateTimeConverter());
|
||||
options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
|
||||
})
|
||||
.ConfigureApiBehaviorOptions(opt => opt.SuppressModelStateInvalidFilter = true);
|
||||
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.AddCorsRegister();
|
||||
builder.Services.AddHttpClient();
|
||||
builder.Services.AddHttpContextAccessor();
|
||||
builder.Services.AddScoped(typeof(BaseRepository<>));
|
||||
builder.Services.AddInfrastructureServices(builder.Configuration, builder.Environment);
|
||||
builder.Services.AddSDKService(builder.Configuration);
|
||||
builder.Services.Configure<MedalRuleConfigSettings>(builder.Configuration.GetSection("MedalRuleConfig"));
|
||||
|
||||
builder.Services.AddCors(options =>
|
||||
{
|
||||
options.AddPolicy("AllowAll", policy =>
|
||||
{
|
||||
policy.AllowAnyOrigin()
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader();
|
||||
});
|
||||
});
|
||||
|
||||
builder.Services.Configure<BrotliCompressionProviderOptions>(options =>
|
||||
{
|
||||
options.Level = System.IO.Compression.CompressionLevel.Optimal;
|
||||
});
|
||||
builder.Services.Configure<GzipCompressionProviderOptions>(options =>
|
||||
{
|
||||
options.Level = System.IO.Compression.CompressionLevel.Fastest;
|
||||
});
|
||||
builder.Services.AddResponseCompression(options =>
|
||||
{
|
||||
options.EnableForHttps = true;
|
||||
options.Providers.Add<BrotliCompressionProvider>();
|
||||
options.Providers.Add<GzipCompressionProvider>();
|
||||
options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(["image/svg+xml", "application/json", "text/plain"]);
|
||||
});
|
||||
|
||||
builder.Services.AddSwaggerGen(option =>
|
||||
{
|
||||
var xmlFile = $"{AppDomain.CurrentDomain.FriendlyName}.xml";
|
||||
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
|
||||
var modelXml = Path.Combine(AppContext.BaseDirectory, "QYZH.InteractiveMagazine.Models.xml");
|
||||
var version = ApiVersionEnum.Wechat;
|
||||
|
||||
option.SwaggerDoc(version.ToString(), new OpenApiInfo
|
||||
{
|
||||
Title = AppDomain.CurrentDomain.FriendlyName,
|
||||
Version = "互动期刊接口文档",
|
||||
Description = $"{version.GetDescription()}接口,Last Modify Time:{new FileInfo(xmlPath).LastWriteTime:yyyy-MM-dd HH:mm:ss}"
|
||||
});
|
||||
|
||||
option.OrderActionsBy(o => o.RelativePath);
|
||||
|
||||
if (File.Exists(xmlPath))
|
||||
{
|
||||
option.IncludeXmlComments(xmlPath, true);
|
||||
}
|
||||
|
||||
if (File.Exists(modelXml))
|
||||
{
|
||||
option.IncludeXmlComments(modelXml, true);
|
||||
}
|
||||
|
||||
option.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
|
||||
{
|
||||
Description = "请输入 Token,格式为 Bearer Token",
|
||||
Name = "Authorization",
|
||||
In = ParameterLocation.Header,
|
||||
Type = SecuritySchemeType.ApiKey,
|
||||
BearerFormat = "JWT",
|
||||
Scheme = "Bearer"
|
||||
});
|
||||
option.AddSecurityRequirement(new OpenApiSecurityRequirement
|
||||
{
|
||||
{
|
||||
new OpenApiSecurityScheme
|
||||
{
|
||||
Reference = new OpenApiReference
|
||||
{
|
||||
Type = ReferenceType.SecurityScheme,
|
||||
Id = "Bearer"
|
||||
}
|
||||
},
|
||||
[]
|
||||
}
|
||||
});
|
||||
|
||||
option.DocInclusionPredicate((docName, apiDesc) =>
|
||||
{
|
||||
if (!apiDesc.TryGetMethodInfo(out var methodInfo))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var groupName = methodInfo.DeclaringType?
|
||||
.GetCustomAttributes(true)
|
||||
.OfType<ApiExplorerSettingsAttribute>()
|
||||
.FirstOrDefault()?
|
||||
.GroupName;
|
||||
|
||||
return groupName == docName;
|
||||
});
|
||||
});
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI(c =>
|
||||
{
|
||||
var version = ApiVersionEnum.Wechat;
|
||||
c.SwaggerEndpoint($"/swagger/{version}/swagger.json", $"{version.GetDescription()}接口");
|
||||
c.DocExpansion(DocExpansion.None);
|
||||
});
|
||||
|
||||
app.UseServiceContext();
|
||||
app.UseHttpsRedirection();
|
||||
app.UseCors("AllowAll");
|
||||
app.UseMiddleware<GlobalExceptionMiddleware>();
|
||||
app.UseMiddleware<OperationLogMiddleware>();
|
||||
app.UseMiddleware<JwtAutoRefreshMiddleware>();
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
app.MapControllers();
|
||||
|
||||
app.Run();
|
||||
@ -0,0 +1,41 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:51131",
|
||||
"sslPort": 44367
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"applicationUrl": "http://localhost:5198",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"applicationUrl": "https://localhost:7253;http://localhost:5198",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Autofac" Version="9.1.0" />
|
||||
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="11.0.0" />
|
||||
<PackageReference Include="BCrypt.Net-Next" Version="4.2.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\QYZH.InteractiveMagazine.IService\QYZH.InteractiveMagazine.IService.csproj" />
|
||||
<ProjectReference Include="..\QYZH.InteractiveMagazine.Service\QYZH.InteractiveMagazine.Service.csproj" />
|
||||
<ProjectReference Include="..\QYZH.InteractiveMagazine.Infrastructure\QYZH.InteractiveMagazine.Infrastructure.csproj" />
|
||||
<ProjectReference Include="..\QYZH.InteractiveMagazine.Common\QYZH.InteractiveMagazine.Common.csproj" />
|
||||
<ProjectReference Include="..\QYZH.InteractiveMagazine.Models\QYZH.InteractiveMagazine.Models.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
71
QYZH.InteractiveMagazine.WeChatApi/appsettings.json
Normal file
71
QYZH.InteractiveMagazine.WeChatApi/appsettings.json
Normal file
@ -0,0 +1,71 @@
|
||||
{
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "server=192.168.20.150;port=13306;database=InteractiveMagazine;user=user;password=n68792bu!y99r905;charset=utf8mb4;"
|
||||
},
|
||||
"JwtSettings": {
|
||||
"Issuer": "QYZH.InteractiveMagazine",
|
||||
"Audience": "QYZH.InteractiveMagazine",
|
||||
"SecretKey": "zG7pLqR9xVw2bN8fYtHk3mPc5sA1dF6eUjW4gXhC7vB",
|
||||
"ExpiryMinutes": 120,
|
||||
"JwtTokenExpiryDays": 30
|
||||
},
|
||||
"RedisSettings": {
|
||||
"ConnectionString": "192.168.20.150:16379,defaultDatabase=5",
|
||||
"Sentinels": [],
|
||||
"ExpireSecondRange": [ 3600, 7200 ]
|
||||
},
|
||||
"RabbitMq": {
|
||||
"HostName": "192.168.20.150",
|
||||
"Port": 5672,
|
||||
"UserName": "smartschool",
|
||||
"Password": "@ss%&*otz%d*pq2S",
|
||||
"VirtualHost": "InteractiveMagazine",
|
||||
"ClientProvidedName": "Custom connection name"
|
||||
},
|
||||
"WeChatSettings": {
|
||||
"AppId": "wx8c08da60bd207e64",
|
||||
"AppSecret": "76fa314c34762c0347bd613e488c6872"
|
||||
},
|
||||
"Serilog": {
|
||||
"MinimumLevel": {
|
||||
"Default": "Information",
|
||||
"Override": {
|
||||
"Microsoft": "Warning",
|
||||
"System": "Warning"
|
||||
}
|
||||
},
|
||||
"WriteTo": [
|
||||
{
|
||||
"Name": "Console"
|
||||
},
|
||||
{
|
||||
"Name": "File",
|
||||
"Args": {
|
||||
"path": "logs/log-.txt",
|
||||
"rollingInterval": "Day"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"AiChat": {
|
||||
"ApiKey": "Ollama",
|
||||
"BaseUrl": "http://172.16.10.130:11434/v1/",
|
||||
"Model": "qwen2.5vl:7b",
|
||||
"TimeoutSeconds": 300,
|
||||
"MaxTokens": 2000,
|
||||
"Temperature": 0.5
|
||||
},
|
||||
"AliyunOSSConfigs": {
|
||||
"AccessKeyID": "LTAI5tEBXGewpHSLiSxyx6Bf",
|
||||
"AccessKeySecret": "w29b8wkw6XQVL8GWXgp3ZesgYeDKvf",
|
||||
"VodBucketName": "outin-5277bbb52bec11f08dbd00163e169e2b.oss-cn-beijing.aliyuncs.com",
|
||||
"BucketName": "qyzh2025test",
|
||||
"Region": "beijing",
|
||||
"RoleArn": "acs:ram::1064745380176636:role/aliyunosstokengeneratorrole",
|
||||
"DurationSeconds": 3600, //过期时间(秒)
|
||||
"Endpoint": "oss-cn-beijing.aliyuncs.com",
|
||||
"ProjectName": "InteractiveMagazine",
|
||||
"Domain": "http://oss-test.qyzhjy.com/"
|
||||
}
|
||||
}
|
||||
43
QYZH.InteractiveMagazine.WeChatApi/medal-rule-config.json
Normal file
43
QYZH.InteractiveMagazine.WeChatApi/medal-rule-config.json
Normal file
@ -0,0 +1,43 @@
|
||||
{
|
||||
"MedalRuleConfig": {
|
||||
"Tables": [
|
||||
{
|
||||
"TableName": "CheckInRecord",
|
||||
"DisplayName": "签到记录",
|
||||
"Fields": [
|
||||
{ "FieldName": "ContinuousDays", "DisplayName": "连续天数" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"TableName": "UserPet",
|
||||
"DisplayName": "宠物",
|
||||
"Fields": [
|
||||
{
|
||||
"FieldName": "GrowthPoints",
|
||||
"DisplayName": "成长值"
|
||||
},
|
||||
{
|
||||
"FieldName": "FeedingCount",
|
||||
"DisplayName": "喂养次数"
|
||||
},
|
||||
{
|
||||
"FieldName": "Comprehension",
|
||||
"DisplayName": "理解力"
|
||||
},
|
||||
{
|
||||
"FieldName": "Judgment",
|
||||
"DisplayName": "判断力"
|
||||
},
|
||||
{
|
||||
"FieldName": "Expression",
|
||||
"DisplayName": "表达力"
|
||||
},
|
||||
{
|
||||
"FieldName": "Persuasiveness",
|
||||
"DisplayName": "说服力"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user