refactor(user-journal): 重构用户期刊关联逻辑,移除枚举依赖并新增二维码功能
1. 删除不再使用的UserJournalTypeEnum枚举 2. 将UserJournal实体的Type字段改为int类型并移除枚举依赖 3. 移除BindJournal相关接口的类型参数校验和赋值逻辑 4. 新增二维码生成和上传功能,为用户期刊绑定QrCodeUrl字段 5. 修复UserAnswerTaskController的用户ID获取逻辑 6. 调整UserJournalQrCodeOutput的字段映射,替换QrCodeContent为QrCodeUrl
This commit is contained in:
473
QYZH.InteractiveMagazine.Common/Helpers/QrCodeHelper.cs
Normal file
473
QYZH.InteractiveMagazine.Common/Helpers/QrCodeHelper.cs
Normal file
@ -0,0 +1,473 @@
|
|||||||
|
using System.IO.Compression;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace QYZH.InteractiveMagazine.Common.Helpers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 二维码帮助类
|
||||||
|
/// </summary>
|
||||||
|
public static class QrCodeHelper
|
||||||
|
{
|
||||||
|
private const int Version = 4;
|
||||||
|
private const int Size = Version * 4 + 17;
|
||||||
|
private const int DataCodewordCount = 80;
|
||||||
|
private const int ErrorCorrectionCodewordCount = 20;
|
||||||
|
private const int QuietZone = 4;
|
||||||
|
private const int Scale = 10;
|
||||||
|
private static readonly int[] AlignmentPatternPositions = [6, 26];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 生成二维码PNG图片字节
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="content">二维码内容</param>
|
||||||
|
/// <returns>PNG图片字节</returns>
|
||||||
|
public static byte[] GeneratePng(string content)
|
||||||
|
{
|
||||||
|
var dataCodewords = CreateDataCodewords(content);
|
||||||
|
var allCodewords = dataCodewords.Concat(CreateErrorCorrectionCodewords(dataCodewords)).ToArray();
|
||||||
|
var modules = BuildModules(allCodewords);
|
||||||
|
var imageSize = (Size + QuietZone * 2) * Scale;
|
||||||
|
var pixels = new byte[imageSize * imageSize * 4];
|
||||||
|
|
||||||
|
for (var i = 0; i < pixels.Length; i += 4)
|
||||||
|
{
|
||||||
|
pixels[i] = 255;
|
||||||
|
pixels[i + 1] = 255;
|
||||||
|
pixels[i + 2] = 255;
|
||||||
|
pixels[i + 3] = 255;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var y = 0; y < Size; y++)
|
||||||
|
{
|
||||||
|
for (var x = 0; x < Size; x++)
|
||||||
|
{
|
||||||
|
if (modules[y, x])
|
||||||
|
{
|
||||||
|
FillModule(pixels, imageSize, x + QuietZone, y + QuietZone);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return EncodePng(imageSize, imageSize, pixels);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] CreateDataCodewords(string content)
|
||||||
|
{
|
||||||
|
var bytes = Encoding.UTF8.GetBytes(content);
|
||||||
|
if (bytes.Length > 78)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("二维码内容过长");
|
||||||
|
}
|
||||||
|
|
||||||
|
var bits = new List<bool>();
|
||||||
|
AppendBits(bits, 0b0100, 4);
|
||||||
|
AppendBits(bits, bytes.Length, 8);
|
||||||
|
foreach (var value in bytes)
|
||||||
|
{
|
||||||
|
AppendBits(bits, value, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
var remaining = DataCodewordCount * 8 - bits.Count;
|
||||||
|
AppendBits(bits, 0, Math.Min(4, remaining));
|
||||||
|
|
||||||
|
while (bits.Count % 8 != 0)
|
||||||
|
{
|
||||||
|
bits.Add(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
var data = BitsToBytes(bits);
|
||||||
|
var pad = true;
|
||||||
|
while (data.Count < DataCodewordCount)
|
||||||
|
{
|
||||||
|
data.Add((byte)(pad ? 0xEC : 0x11));
|
||||||
|
pad = !pad;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool[,] BuildModules(byte[] codewords)
|
||||||
|
{
|
||||||
|
var modules = new bool[Size, Size];
|
||||||
|
var reserved = new bool[Size, Size];
|
||||||
|
|
||||||
|
AddFinder(modules, reserved, 0, 0);
|
||||||
|
AddFinder(modules, reserved, Size - 7, 0);
|
||||||
|
AddFinder(modules, reserved, 0, Size - 7);
|
||||||
|
AddTimingPatterns(modules, reserved);
|
||||||
|
AddAlignmentPatterns(modules, reserved);
|
||||||
|
ReserveFormatAreas(reserved);
|
||||||
|
|
||||||
|
modules[Version * 4 + 9, 8] = true;
|
||||||
|
reserved[Version * 4 + 9, 8] = true;
|
||||||
|
|
||||||
|
AddDataModules(modules, reserved, codewords);
|
||||||
|
AddFormatBits(modules);
|
||||||
|
|
||||||
|
return modules;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddFinder(bool[,] modules, bool[,] reserved, int left, int top)
|
||||||
|
{
|
||||||
|
for (var y = -1; y <= 7; y++)
|
||||||
|
{
|
||||||
|
for (var x = -1; x <= 7; x++)
|
||||||
|
{
|
||||||
|
var px = left + x;
|
||||||
|
var py = top + y;
|
||||||
|
if (px < 0 || py < 0 || px >= Size || py >= Size)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
reserved[py, px] = true;
|
||||||
|
modules[py, px] = x >= 0 && x <= 6 && y >= 0 && y <= 6
|
||||||
|
&& (x == 0 || x == 6 || y == 0 || y == 6 || (x >= 2 && x <= 4 && y >= 2 && y <= 4));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddTimingPatterns(bool[,] modules, bool[,] reserved)
|
||||||
|
{
|
||||||
|
for (var i = 8; i < Size - 8; i++)
|
||||||
|
{
|
||||||
|
var isDark = i % 2 == 0;
|
||||||
|
modules[6, i] = isDark;
|
||||||
|
modules[i, 6] = isDark;
|
||||||
|
reserved[6, i] = true;
|
||||||
|
reserved[i, 6] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddAlignmentPatterns(bool[,] modules, bool[,] reserved)
|
||||||
|
{
|
||||||
|
foreach (var centerY in AlignmentPatternPositions)
|
||||||
|
{
|
||||||
|
foreach (var centerX in AlignmentPatternPositions)
|
||||||
|
{
|
||||||
|
if (reserved[centerY, centerX])
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var y = -2; y <= 2; y++)
|
||||||
|
{
|
||||||
|
for (var x = -2; x <= 2; x++)
|
||||||
|
{
|
||||||
|
var px = centerX + x;
|
||||||
|
var py = centerY + y;
|
||||||
|
reserved[py, px] = true;
|
||||||
|
modules[py, px] = Math.Max(Math.Abs(x), Math.Abs(y)) != 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ReserveFormatAreas(bool[,] reserved)
|
||||||
|
{
|
||||||
|
for (var i = 0; i <= 8; i++)
|
||||||
|
{
|
||||||
|
reserved[8, i] = true;
|
||||||
|
reserved[i, 8] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < 8; i++)
|
||||||
|
{
|
||||||
|
reserved[8, Size - 1 - i] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < 7; i++)
|
||||||
|
{
|
||||||
|
reserved[Size - 1 - i, 8] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddDataModules(bool[,] modules, bool[,] reserved, byte[] codewords)
|
||||||
|
{
|
||||||
|
var bits = new List<bool>();
|
||||||
|
foreach (var codeword in codewords)
|
||||||
|
{
|
||||||
|
AppendBits(bits, codeword, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
var bitIndex = 0;
|
||||||
|
var upward = true;
|
||||||
|
for (var right = Size - 1; right >= 1; right -= 2)
|
||||||
|
{
|
||||||
|
if (right == 6)
|
||||||
|
{
|
||||||
|
right--;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < Size; i++)
|
||||||
|
{
|
||||||
|
var y = upward ? Size - 1 - i : i;
|
||||||
|
for (var j = 0; j < 2; j++)
|
||||||
|
{
|
||||||
|
var x = right - j;
|
||||||
|
if (reserved[y, x])
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bit = bitIndex < bits.Count && bits[bitIndex++];
|
||||||
|
if ((x + y) % 2 == 0)
|
||||||
|
{
|
||||||
|
bit = !bit;
|
||||||
|
}
|
||||||
|
|
||||||
|
modules[y, x] = bit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
upward = !upward;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddFormatBits(bool[,] modules)
|
||||||
|
{
|
||||||
|
var format = GetFormatBits();
|
||||||
|
|
||||||
|
for (var i = 0; i <= 5; i++)
|
||||||
|
{
|
||||||
|
modules[i, 8] = GetBit(format, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
modules[7, 8] = GetBit(format, 6);
|
||||||
|
modules[8, 8] = GetBit(format, 7);
|
||||||
|
modules[8, 7] = GetBit(format, 8);
|
||||||
|
|
||||||
|
for (var i = 9; i < 15; i++)
|
||||||
|
{
|
||||||
|
modules[8, 14 - i] = GetBit(format, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < 8; i++)
|
||||||
|
{
|
||||||
|
modules[8, Size - 1 - i] = GetBit(format, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 8; i < 15; i++)
|
||||||
|
{
|
||||||
|
modules[Size - 15 + i, 8] = GetBit(format, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
modules[Size - 8, 8] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int GetFormatBits()
|
||||||
|
{
|
||||||
|
const int errorCorrectionLevelBits = 1;
|
||||||
|
const int mask = 0;
|
||||||
|
var data = (errorCorrectionLevelBits << 3) | mask;
|
||||||
|
var value = data << 10;
|
||||||
|
const int generator = 0x537;
|
||||||
|
|
||||||
|
for (var i = 14; i >= 10; i--)
|
||||||
|
{
|
||||||
|
if (((value >> i) & 1) != 0)
|
||||||
|
{
|
||||||
|
value ^= generator << (i - 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ((data << 10) | (value & 0x3FF)) ^ 0x5412;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] CreateErrorCorrectionCodewords(byte[] data)
|
||||||
|
{
|
||||||
|
var generator = CreateGeneratorPolynomial(ErrorCorrectionCodewordCount);
|
||||||
|
var result = new byte[ErrorCorrectionCodewordCount];
|
||||||
|
|
||||||
|
foreach (var b in data)
|
||||||
|
{
|
||||||
|
var factor = b ^ result[0];
|
||||||
|
Array.Copy(result, 1, result, 0, result.Length - 1);
|
||||||
|
result[^1] = 0;
|
||||||
|
|
||||||
|
for (var i = 0; i < result.Length; i++)
|
||||||
|
{
|
||||||
|
result[i] ^= GaloisMultiply(generator[i + 1], factor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] CreateGeneratorPolynomial(int degree)
|
||||||
|
{
|
||||||
|
var result = new List<byte> { 1 };
|
||||||
|
for (var i = 0; i < degree; i++)
|
||||||
|
{
|
||||||
|
var next = new byte[result.Count + 1];
|
||||||
|
for (var j = 0; j < result.Count; j++)
|
||||||
|
{
|
||||||
|
next[j] ^= result[j];
|
||||||
|
next[j + 1] ^= GaloisMultiply(result[j], GaloisPower(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
result = next.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte GaloisPower(int exponent)
|
||||||
|
{
|
||||||
|
var value = 1;
|
||||||
|
for (var i = 0; i < exponent; i++)
|
||||||
|
{
|
||||||
|
value <<= 1;
|
||||||
|
if ((value & 0x100) != 0)
|
||||||
|
{
|
||||||
|
value ^= 0x11D;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (byte)value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte GaloisMultiply(int x, int y)
|
||||||
|
{
|
||||||
|
var result = 0;
|
||||||
|
while (y != 0)
|
||||||
|
{
|
||||||
|
if ((y & 1) != 0)
|
||||||
|
{
|
||||||
|
result ^= x;
|
||||||
|
}
|
||||||
|
|
||||||
|
x <<= 1;
|
||||||
|
if ((x & 0x100) != 0)
|
||||||
|
{
|
||||||
|
x ^= 0x11D;
|
||||||
|
}
|
||||||
|
|
||||||
|
y >>= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (byte)result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AppendBits(List<bool> bits, int value, int count)
|
||||||
|
{
|
||||||
|
for (var i = count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
bits.Add(((value >> i) & 1) != 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<byte> BitsToBytes(List<bool> bits)
|
||||||
|
{
|
||||||
|
var result = new List<byte>();
|
||||||
|
for (var i = 0; i < bits.Count; i += 8)
|
||||||
|
{
|
||||||
|
var value = 0;
|
||||||
|
for (var j = 0; j < 8; j++)
|
||||||
|
{
|
||||||
|
value = (value << 1) | (bits[i + j] ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Add((byte)value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool GetBit(int value, int index)
|
||||||
|
{
|
||||||
|
return ((value >> index) & 1) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void FillModule(byte[] pixels, int imageSize, int moduleX, int moduleY)
|
||||||
|
{
|
||||||
|
var startX = moduleX * Scale;
|
||||||
|
var startY = moduleY * Scale;
|
||||||
|
|
||||||
|
for (var y = 0; y < Scale; y++)
|
||||||
|
{
|
||||||
|
for (var x = 0; x < Scale; x++)
|
||||||
|
{
|
||||||
|
var index = ((startY + y) * imageSize + startX + x) * 4;
|
||||||
|
pixels[index] = 0;
|
||||||
|
pixels[index + 1] = 0;
|
||||||
|
pixels[index + 2] = 0;
|
||||||
|
pixels[index + 3] = 255;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] EncodePng(int width, int height, byte[] rgba)
|
||||||
|
{
|
||||||
|
using var output = new MemoryStream();
|
||||||
|
WriteUInt(output, 0x89504E47);
|
||||||
|
WriteUInt(output, 0x0D0A1A0A);
|
||||||
|
|
||||||
|
using (var ihdr = new MemoryStream())
|
||||||
|
{
|
||||||
|
WriteUInt(ihdr, (uint)width);
|
||||||
|
WriteUInt(ihdr, (uint)height);
|
||||||
|
ihdr.WriteByte(8);
|
||||||
|
ihdr.WriteByte(6);
|
||||||
|
ihdr.WriteByte(0);
|
||||||
|
ihdr.WriteByte(0);
|
||||||
|
ihdr.WriteByte(0);
|
||||||
|
WriteChunk(output, "IHDR", ihdr.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
var stride = width * 4;
|
||||||
|
using (var raw = new MemoryStream())
|
||||||
|
{
|
||||||
|
for (var y = 0; y < height; y++)
|
||||||
|
{
|
||||||
|
raw.WriteByte(0);
|
||||||
|
raw.Write(rgba, y * stride, stride);
|
||||||
|
}
|
||||||
|
|
||||||
|
using var compressed = new MemoryStream();
|
||||||
|
using (var zlib = new ZLibStream(compressed, CompressionLevel.SmallestSize, true))
|
||||||
|
{
|
||||||
|
raw.Position = 0;
|
||||||
|
raw.CopyTo(zlib);
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteChunk(output, "IDAT", compressed.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteChunk(output, "IEND", []);
|
||||||
|
return output.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void WriteChunk(Stream stream, string type, byte[] data)
|
||||||
|
{
|
||||||
|
WriteUInt(stream, (uint)data.Length);
|
||||||
|
var typeBytes = Encoding.ASCII.GetBytes(type);
|
||||||
|
stream.Write(typeBytes);
|
||||||
|
stream.Write(data);
|
||||||
|
WriteUInt(stream, Crc32(typeBytes.Concat(data).ToArray()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void WriteUInt(Stream stream, uint value)
|
||||||
|
{
|
||||||
|
stream.WriteByte((byte)(value >> 24));
|
||||||
|
stream.WriteByte((byte)(value >> 16));
|
||||||
|
stream.WriteByte((byte)(value >> 8));
|
||||||
|
stream.WriteByte((byte)value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static uint Crc32(byte[] bytes)
|
||||||
|
{
|
||||||
|
var crc = 0xFFFFFFFFu;
|
||||||
|
foreach (var b in bytes)
|
||||||
|
{
|
||||||
|
crc ^= b;
|
||||||
|
for (var i = 0; i < 8; i++)
|
||||||
|
{
|
||||||
|
crc = (crc & 1) == 1 ? (crc >> 1) ^ 0xEDB88320u : crc >> 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ~crc;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -17,10 +17,6 @@ public class BindJournalInput
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public long Id { get; set; }
|
public long Id { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 关联类型: Read(已读), Favorite(收藏), Subscribe(订阅),默认 Subscribe
|
|
||||||
/// </summary>
|
|
||||||
public string Type { get; set; } = UserJournalTypeEnum.Subscribe.ToString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -121,10 +117,6 @@ public class CreateUserJournalQrCodeInput
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public long JournalId { get; set; }
|
public long JournalId { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 关联类型: Read, Favorite, Subscribe,默认 Subscribe
|
|
||||||
/// </summary>
|
|
||||||
public string Type { get; set; } = UserJournalTypeEnum.Subscribe.ToString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -201,7 +193,7 @@ public class UserJournalQrCodeOutput
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 二维码内容
|
/// 二维码内容
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string QrCodeContent { get; set; } = string.Empty;
|
public string QrCodeUrl { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 创建时间
|
/// 创建时间
|
||||||
|
|||||||
@ -31,12 +31,18 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
|||||||
public long JournalId { get; set; }
|
public long JournalId { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Desc:关联类型: Read(已读), Favorite(收藏), Subscribe(订阅)
|
/// Desc:关联类型: 默认0
|
||||||
/// Default:Read
|
|
||||||
/// Nullable:False
|
/// Nullable:False
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[SugarColumn(ColumnName = "Type")]
|
[SugarColumn(ColumnName = "Type")]
|
||||||
public UserJournalTypeEnum Type { get; set; }
|
public int Type { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:二维码地址
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:True
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(ColumnName = "QrCodeUrl")]
|
||||||
|
public string? QrCodeUrl { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,25 +0,0 @@
|
|||||||
using System.ComponentModel;
|
|
||||||
|
|
||||||
namespace QYZH.InteractiveMagazine.Models.Enum;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 用户期刊关联类型枚举
|
|
||||||
/// </summary>
|
|
||||||
public enum UserJournalTypeEnum
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 已读
|
|
||||||
/// </summary>
|
|
||||||
[Description("已读")]
|
|
||||||
Read = 1,
|
|
||||||
/// <summary>
|
|
||||||
/// 收藏
|
|
||||||
/// </summary>
|
|
||||||
[Description("收藏")]
|
|
||||||
Favorite = 2,
|
|
||||||
/// <summary>
|
|
||||||
/// 订阅
|
|
||||||
/// </summary>
|
|
||||||
[Description("订阅")]
|
|
||||||
Subscribe = 3
|
|
||||||
}
|
|
||||||
@ -1,4 +1,6 @@
|
|||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using QYZH.InteractiveMagazine.Common.Helpers;
|
||||||
|
using QYZH.InteractiveMagazine.Infrastructure.OSS;
|
||||||
using QYZH.InteractiveMagazine.Infrastructure.RabbitMQ;
|
using QYZH.InteractiveMagazine.Infrastructure.RabbitMQ;
|
||||||
using QYZH.InteractiveMagazine.IService;
|
using QYZH.InteractiveMagazine.IService;
|
||||||
using QYZH.InteractiveMagazine.Models.Common;
|
using QYZH.InteractiveMagazine.Models.Common;
|
||||||
@ -20,6 +22,7 @@ public class UserJournalService(
|
|||||||
BaseRepository<Journal> journalRepository,
|
BaseRepository<Journal> journalRepository,
|
||||||
ILogger<UserJournalService> logger,
|
ILogger<UserJournalService> logger,
|
||||||
IRabbitMQService rabbitMqService,
|
IRabbitMQService rabbitMqService,
|
||||||
|
OssService ossService,
|
||||||
IPetService petService)
|
IPetService petService)
|
||||||
: BaseRepository<UserJournal>, IUserJournalService
|
: BaseRepository<UserJournal>, IUserJournalService
|
||||||
{
|
{
|
||||||
@ -32,8 +35,6 @@ public class UserJournalService(
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public async Task<BindJournalOutput> BindJournalAsync(long userId, BindJournalInput input)
|
public async Task<BindJournalOutput> BindJournalAsync(long userId, BindJournalInput input)
|
||||||
{
|
{
|
||||||
logger.LogInformation("用户绑定期刊,UserId: {UserId}, JournalId: {JournalId}, Id: {Id}, Type: {Type}",
|
|
||||||
userId, input.JournalId, input.Id, input.Type);
|
|
||||||
|
|
||||||
// 校验参数
|
// 校验参数
|
||||||
if (input.JournalId <= 0|| input.Id <= 0)
|
if (input.JournalId <= 0|| input.Id <= 0)
|
||||||
@ -63,17 +64,10 @@ public class UserJournalService(
|
|||||||
throw new BusinessException("该期刊暂未发布,无法绑定", ResultCode.UNPROCESSABLE_ENTITY);
|
throw new BusinessException("该期刊暂未发布,无法绑定", ResultCode.UNPROCESSABLE_ENTITY);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 防重复绑定:同一用户 + 期刊 + 实例 + 类型
|
|
||||||
if (!Enum.TryParse<UserJournalTypeEnum>(input.Type, true, out var bindType))
|
|
||||||
{
|
|
||||||
throw new BusinessException("关联类型不正确", ResultCode.BAD_REQUEST);
|
|
||||||
}
|
|
||||||
|
|
||||||
var userJournal = await userJournalRepository.Queryable()
|
var userJournal = await userJournalRepository.Queryable()
|
||||||
.Where(uj => uj.Id == input.Id && uj.JournalId == input.JournalId && !uj.IsDeleted)
|
.Where(uj => uj.Id == input.Id && uj.JournalId == input.JournalId && !uj.IsDeleted)
|
||||||
.FirstAsync();
|
.FirstAsync();
|
||||||
|
|
||||||
// 检查是否为首次绑定期刊(用于激活宠物)
|
|
||||||
if (userJournal == null)
|
if (userJournal == null)
|
||||||
{
|
{
|
||||||
logger.LogWarning("绑定期刊失败,二维码记录不存在,UserId: {UserId}, JournalId: {JournalId}, Id: {Id}", userId, input.JournalId, input.Id);
|
logger.LogWarning("绑定期刊失败,二维码记录不存在,UserId: {UserId}, JournalId: {JournalId}, Id: {Id}", userId, input.JournalId, input.Id);
|
||||||
@ -97,7 +91,6 @@ public class UserJournalService(
|
|||||||
// 创建绑定记录
|
// 创建绑定记录
|
||||||
var updateCount = await userJournalRepository.Updateable()
|
var updateCount = await userJournalRepository.Updateable()
|
||||||
.SetColumns(uj => uj.UserId == userId)
|
.SetColumns(uj => uj.UserId == userId)
|
||||||
.SetColumns(uj => uj.Type == bindType)
|
|
||||||
.SetColumns(uj => uj.UpdatedBy == userId.ToString())
|
.SetColumns(uj => uj.UpdatedBy == userId.ToString())
|
||||||
.SetColumns(uj => uj.UpdatedAt == DateTime.Now)
|
.SetColumns(uj => uj.UpdatedAt == DateTime.Now)
|
||||||
.Where(uj => uj.Id == input.Id && !uj.IsDeleted && (uj.UserId == null || uj.UserId == 0))
|
.Where(uj => uj.Id == input.Id && !uj.IsDeleted && (uj.UserId == null || uj.UserId == 0))
|
||||||
@ -111,7 +104,6 @@ public class UserJournalService(
|
|||||||
|
|
||||||
logger.LogInformation("用户绑定期刊成功,UserId: {UserId}, JournalId: {JournalId}, Id: {Id}", userId, input.JournalId, userJournal.Id);
|
logger.LogInformation("用户绑定期刊成功,UserId: {UserId}, JournalId: {JournalId}, Id: {Id}", userId, input.JournalId, userJournal.Id);
|
||||||
userJournal.UserId = userId;
|
userJournal.UserId = userId;
|
||||||
userJournal.Type = bindType;
|
|
||||||
userJournal.UpdatedBy = userId.ToString();
|
userJournal.UpdatedBy = userId.ToString();
|
||||||
userJournal.UpdatedAt = DateTime.Now;
|
userJournal.UpdatedAt = DateTime.Now;
|
||||||
|
|
||||||
@ -152,11 +144,6 @@ public class UserJournalService(
|
|||||||
throw new BusinessException("期刊Id不能为空", ResultCode.BAD_REQUEST);
|
throw new BusinessException("期刊Id不能为空", ResultCode.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Enum.TryParse<UserJournalTypeEnum>(input.Type, true, out var type))
|
|
||||||
{
|
|
||||||
throw new BusinessException("关联类型不正确", ResultCode.BAD_REQUEST);
|
|
||||||
}
|
|
||||||
|
|
||||||
var journal = await journalRepository.GetByIdAsync(input.JournalId);
|
var journal = await journalRepository.GetByIdAsync(input.JournalId);
|
||||||
if (journal == null || journal.IsDeleted)
|
if (journal == null || journal.IsDeleted)
|
||||||
{
|
{
|
||||||
@ -172,7 +159,7 @@ public class UserJournalService(
|
|||||||
{
|
{
|
||||||
UserId = null,
|
UserId = null,
|
||||||
JournalId = input.JournalId,
|
JournalId = input.JournalId,
|
||||||
Type = type,
|
Type = 0,
|
||||||
Status = (int)UserJournalStatusEnum.Active,
|
Status = (int)UserJournalStatusEnum.Active,
|
||||||
IsDeleted = false,
|
IsDeleted = false,
|
||||||
CreatedBy = operatorId.ToString(),
|
CreatedBy = operatorId.ToString(),
|
||||||
@ -181,6 +168,17 @@ public class UserJournalService(
|
|||||||
UpdatedAt = DateTime.Now
|
UpdatedAt = DateTime.Now
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var qrCodeContent = BuildQrCodeContent(record.JournalId, record.Id);
|
||||||
|
var qrCodeKey = $"journal/qrcode/{record.JournalId}/{record.Id}.png";
|
||||||
|
using var qrCodeStream = new MemoryStream(QrCodeHelper.GeneratePng(qrCodeContent));
|
||||||
|
var uploadedKey = ossService.PutObject(qrCodeKey, qrCodeStream);
|
||||||
|
if (string.IsNullOrWhiteSpace(uploadedKey))
|
||||||
|
{
|
||||||
|
throw new BusinessException("二维码图片上传失败,请稍后重试", ResultCode.GLOBAL_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
record.QrCodeUrl = uploadedKey;
|
||||||
|
|
||||||
var result = await userJournalRepository.InsertAsync(record);
|
var result = await userJournalRepository.InsertAsync(record);
|
||||||
if (!result)
|
if (!result)
|
||||||
{
|
{
|
||||||
@ -322,7 +320,7 @@ public class UserJournalService(
|
|||||||
Type = record.Type.ToString(),
|
Type = record.Type.ToString(),
|
||||||
Status = record.Status.ToString(),
|
Status = record.Status.ToString(),
|
||||||
IsBound = record.UserId.HasValue && record.UserId.Value > 0,
|
IsBound = record.UserId.HasValue && record.UserId.Value > 0,
|
||||||
QrCodeContent = BuildQrCodeContent(record.JournalId, record.Id),
|
QrCodeUrl = DomainHelper.OssFullUrl(record.QrCodeUrl ?? string.Empty),
|
||||||
CreatedAt = record.CreatedAt,
|
CreatedAt = record.CreatedAt,
|
||||||
BoundAt = record.UserId.HasValue && record.UserId.Value > 0 ? record.UpdatedAt : null
|
BoundAt = record.UserId.HasValue && record.UserId.Value > 0 ? record.UpdatedAt : null
|
||||||
};
|
};
|
||||||
|
|||||||
@ -239,7 +239,8 @@ public class UserAnswerTaskController : WeChatBaseController
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var userId = ConstUserId;//GetCurrentUserId();
|
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
if (userId == 0)
|
if (userId == 0)
|
||||||
{
|
{
|
||||||
return BaseResponse<BatchClaimPointsOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
return BaseResponse<BatchClaimPointsOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||||
|
|||||||
Reference in New Issue
Block a user