diff --git a/Server/Controller/BaseController.cs b/Server/Controller/BaseController.cs
index 3825fe6..a9a4f61 100644
--- a/Server/Controller/BaseController.cs
+++ b/Server/Controller/BaseController.cs
@@ -1,6 +1,8 @@
using Blahblah.FlowerStory.Server.Data;
using Blahblah.FlowerStory.Server.Data.Model;
using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
+using System.ComponentModel.DataAnnotations;
using System.Security.Cryptography;
using System.Text;
@@ -9,10 +11,23 @@ namespace Blahblah.FlowerStory.Server.Controller;
///
/// 基础服务抽象类
///
-public abstract class BaseController : ControllerBase
+public abstract partial class BaseController : ControllerBase
{
private const string Salt = "Blah blah, o! Flower story, intimately help you record every bit of the garden.";
+ ///
+ /// 支持的图片文件签名
+ ///
+ protected static readonly List PhotoSignatures = new()
+ {
+ // jpeg
+ new byte[] { 0xFF, 0xD8, 0xFF, 0xE0 },
+ new byte[] { 0xFF, 0xD8, 0xFF, 0xE2 },
+ new byte[] { 0xFF, 0xD8, 0xFF, 0xE3 },
+ // png
+ new byte[] { 0x89, 0x50, 0x4E, 0x47 }
+ };
+
///
/// 自定义认证头的关键字
///
@@ -114,7 +129,7 @@ public abstract class BaseController : ControllerBase
logger?.LogWarning("token \"{hash}\" has expired after {date}", token.Id, token.ExpireDate);
return (Unauthorized(), null);
}
- var user = database.Users.Find(token.UserId);
+ var user = QueryUserItem(token.UserId);
if (user == null)
{
logger?.LogWarning("user not found with id {id}", token.UserId);
@@ -133,10 +148,8 @@ public abstract class BaseController : ControllerBase
{
token.ExpireDateUnixTime = expires;
}
- database.Tokens.Update(token);
user.ActiveDateUnixTime = now.ToUnixTimeMilliseconds();
- database.Users.Update(user);
}
return (null, user);
@@ -159,4 +172,62 @@ public abstract class BaseController : ControllerBase
}
return count;
}
+
+ ///
+ /// 读取文件到 byte 数组
+ ///
+ /// 来自请求的文件
+ ///
+ protected FileResult? WrapFormFile(IFormFile file)
+ {
+ if (file == null)
+ {
+ return null;
+ }
+ using var stream = file.OpenReadStream();
+
+ // check header
+ var headers = new byte[PhotoSignatures.Max(s => s.Length)];
+ int count = stream.Read(headers, 0, headers.Length);
+ if (PhotoSignatures.Any(s => headers.Take(s.Length).SequenceEqual(s)))
+ {
+ using var ms = new MemoryStream();
+ ms.Write(headers, 0, count);
+
+ // reading
+ const int size = 16384;
+ var buffer = new byte[size];
+ while ((count = stream.Read(buffer, 0, size)) > 0)
+ {
+ ms.Write(buffer, 0, count);
+ }
+ var data = ms.ToArray();
+ var name = file.FileName;
+ name = $"{Path.GetFileNameWithoutExtension(name)}_{DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()}.{Path.GetExtension(name)}";
+
+ return new FileResult
+ {
+ Filename = name,
+ Content = data
+ };
+ }
+ return null;
+ }
+}
+
+///
+/// 文件结果
+///
+public record FileResult
+{
+ ///
+ /// 文件名
+ ///
+ public string? Filename { get; init; }
+
+ ///
+ /// 文件内容
+ ///
+ [Required]
+ public required byte[] Content { get; init; }
}
diff --git a/Server/Controller/BaseController.sqlite.cs b/Server/Controller/BaseController.sqlite.cs
new file mode 100644
index 0000000..3ea3b3c
--- /dev/null
+++ b/Server/Controller/BaseController.sqlite.cs
@@ -0,0 +1,75 @@
+using Blahblah.FlowerStory.Server.Data.Model;
+using Microsoft.EntityFrameworkCore;
+
+namespace Blahblah.FlowerStory.Server.Controller;
+
+partial class BaseController
+{
+
+ ///
+ /// 根据 uid 获取用户对象
+ ///
+ /// 用户唯一 id
+ ///
+ protected UserItem? QueryUserItem(int uid)
+ {
+ return database.Users
+ .FromSql($"SELECT \"uid\",\"id\",\"\" AS \"password\",\"level\",\"regdate\",\"activedate\",\"name\",\"email\",\"mobile\",NULL AS \"avatar\" FROM [users] WHERE \"uid\" = {uid} LIMIT 1")
+ .SingleOrDefault();
+ }
+
+ ///
+ /// 根据 id 获取登录使用的用户对象
+ ///
+ /// 用户 id
+ ///
+ protected UserItem? QueryUserItemForAuthentication(string id)
+ {
+ return database.Users
+ .FromSql($"SELECT \"uid\",\"id\",\"password\",0 AS \"level\",0 AS \"regdate\",\"activedate\",\"\" AS \"name\",NULL AS \"email\",NULL AS \"mobile\",NULL as \"avatar\" FROM [users] WHERE \"id\" = {id} LIMIT 1")
+ .SingleOrDefault();
+ }
+
+ ///
+ /// 获取用户头像数据
+ ///
+ /// 用户唯一 id
+ ///
+ protected byte[]? QueryUserAvatar(int uid)
+ {
+ return database.Database.SqlQuery($"SELECT \"avatar\" AS \"Value\" FROM \"users\" WHERE \"uid\" = {uid} LIMIT 1").SingleOrDefault();
+ }
+
+ ///
+ /// 移除用户头像
+ ///
+ /// 用户唯一 id
+ ///
+ protected int RemoveUserAvatar(int uid)
+ {
+ return database.Database.ExecuteSql($"UPDATE \"users\" SET \"avatar\" = NULL WHERE \"uid\" = {uid}");
+ }
+
+ ///
+ /// 移除用户名下的花草
+ ///
+ /// 用户唯一 id
+ /// 花草唯一 id
+ ///
+ protected int RemoveUserFlower(int uid, int fid)
+ {
+ return database.Database.ExecuteSql($"DELETE FROM \"flowers\" WHERE \"uid\" = {uid} AND \"fid\" = {fid}");
+ }
+
+ ///
+ /// 批量移除用户名下的花草
+ ///
+ /// 用户唯一 id
+ /// 花草唯一 id 的数组
+ ///
+ protected int RemoveUserFlowers(int uid, int[] fids)
+ {
+ var idfilter = string.Join(", ", fids);
+ return database.Database.ExecuteSql($"DELETE FROM \"flowers\" WHERE \"uid\" = {uid} AND \"fid\" IN ({idfilter})");
+ }
+}
diff --git a/Server/Controller/EventController.cs b/Server/Controller/EventController.cs
index e80ddb5..a8ebc52 100644
--- a/Server/Controller/EventController.cs
+++ b/Server/Controller/EventController.cs
@@ -8,7 +8,6 @@ namespace Blahblah.FlowerStory.Server.Controller;
/// 事件相关服务
///
[ApiController]
-[Consumes("application/json")]
[Produces("application/json")]
[Route("api/event")]
public class EventController : BaseController
@@ -46,7 +45,7 @@ public class EventController : BaseController
/// 未找到登录会话或已过期
/// 用户已禁用
/// 未找到关联用户
- [Route("query")]
+ [Route("query", Name = "queryEvents")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
diff --git a/Server/Controller/FlowerController.cs b/Server/Controller/FlowerController.cs
index 9484808..507c632 100644
--- a/Server/Controller/FlowerController.cs
+++ b/Server/Controller/FlowerController.cs
@@ -10,7 +10,6 @@ namespace Blahblah.FlowerStory.Server.Controller;
/// 花草相关服务
///
[ApiController]
-[Consumes("application/json")]
[Produces("application/json")]
[Route("api/flower")]
public class FlowerController : BaseController
@@ -50,7 +49,7 @@ public class FlowerController : BaseController
/// 未找到登录会话或已过期
/// 用户已禁用
/// 未找到关联用户
- [Route("query")]
+ [Route("query", Name = "queryFlowers")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
@@ -128,7 +127,7 @@ public class FlowerController : BaseController
/// 未找到登录会话或已过期
/// 用户已禁用
/// 未找到关联用户
- [Route("remove")]
+ [Route("remove", Name = "removeFlower")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
@@ -146,7 +145,7 @@ public class FlowerController : BaseController
return NotFound();
}
- var count = database.Database.ExecuteSql($"DELETE FROM [flowers] WHERE \"uid\" = {user.Id} AND \"fid\" = {id}");
+ var count = RemoveUserFlower(user.Id, id);
SaveDatabase();
@@ -172,12 +171,13 @@ public class FlowerController : BaseController
/// 未找到登录会话或已过期
/// 用户已禁用
/// 未找到关联用户
- [Route("removeany")]
+ [Route("removeany", Name = "removeFlowers")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[HttpPost]
+ [Consumes("application/json")]
public ActionResult RemoveFlower([FromBody] int[] ids)
{
var (result, user) = CheckPermission();
@@ -190,8 +190,7 @@ public class FlowerController : BaseController
return NotFound();
}
- var idfilter = string.Join(", ", ids);
- var count = database.Database.ExecuteSql($"DELETE FROM [flowers] WHERE \"uid\" = {user.Id} AND \"fid\" IN ({idfilter})");
+ var count = RemoveUserFlowers(user.Id, ids);
SaveDatabase();
@@ -221,12 +220,13 @@ public class FlowerController : BaseController
/// 未找到登录会话或已过期
/// 用户已禁用
/// 未找到关联用户
- [Route("add")]
+ [Route("add", Name = "addFlower")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[HttpPost]
+ [Consumes("application/json")]
public ActionResult AddFlower([FromBody] FlowerParameter flower)
{
var (result, user) = CheckPermission();
@@ -278,12 +278,13 @@ public class FlowerController : BaseController
/// 未找到登录会话或已过期
/// 用户已禁用
/// 未找到关联用户或者未找到将修改的花草对象
- [Route("update")]
+ [Route("update", Name = "updateFlower")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[HttpPut]
+ [Consumes("application/json")]
public ActionResult Update([FromBody] FlowerUpdateParameter update)
{
var (result, user) = CheckPermission();
@@ -296,7 +297,7 @@ public class FlowerController : BaseController
return NotFound();
}
- var flower = database.Flowers.FirstOrDefault(f => f.Id == update.Id && f.OwnerId == user.Id);
+ var flower = database.Flowers.SingleOrDefault(f => f.Id == update.Id && f.OwnerId == user.Id);
if (flower == null)
{
return NotFound(update.Id);
@@ -306,7 +307,6 @@ public class FlowerController : BaseController
flower.DateBuyUnixTime = update.DateBuy;
flower.Cost = update.Cost;
flower.Purchase = update.Purchase;
- database.Flowers.Update(flower);
SaveDatabase();
return Ok(user);
diff --git a/Server/Controller/ImageController.cs b/Server/Controller/ImageController.cs
new file mode 100644
index 0000000..3d1a815
--- /dev/null
+++ b/Server/Controller/ImageController.cs
@@ -0,0 +1,55 @@
+using Blahblah.FlowerStory.Server.Data;
+using Microsoft.AspNetCore.Mvc;
+
+namespace Blahblah.FlowerStory.Server.Controller;
+
+///
+/// 图片相关服务
+///
+[Produces("image/png")]
+[Route("photo")]
+public class ImageController : BaseController
+{
+ ///
+ public ImageController(FlowerDatabase database, ILogger? logger = null) : base(database, logger)
+ {
+ }
+
+ ///
+ /// 请求用户头像
+ ///
+ ///
+ /// 请求示例:
+ ///
+ /// GET /photo/avatar
+ /// Authorization: authorization id
+ ///
+ ///
+ /// 认证通过则显示用户头像
+ /// 返回头像
+ /// 认证失败
+ /// 未找到头像
+ [Route("avatar", Name = "getAvatar")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status401Unauthorized)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ [HttpGet]
+ public ActionResult GetUserAvatar()
+ {
+ var (result, token) = CheckToken();
+ if (result != null)
+ {
+ return result;
+ }
+ if (token == null)
+ {
+ return Unauthorized();
+ }
+ var avatar = QueryUserAvatar(token.UserId);
+ if (avatar == null)
+ {
+ return NotFound();
+ }
+ return File(avatar, "image/png");
+ }
+}
diff --git a/Server/Controller/UserController.cs b/Server/Controller/UserController.cs
index e5ed4e7..0d490a3 100644
--- a/Server/Controller/UserController.cs
+++ b/Server/Controller/UserController.cs
@@ -9,7 +9,6 @@ namespace Blahblah.FlowerStory.Server.Controller
/// 用户会话相关服务
///
[ApiController]
- [Consumes("application/json")]
[Produces("application/json")]
[Route("api/user")]
public partial class UserController : BaseController
@@ -37,17 +36,19 @@ namespace Blahblah.FlowerStory.Server.Controller
/// 返回自定义认证头
/// 认证失败
/// 未找到用户
- [Route("auth")]
+ [Route("auth", Name = "authenticate")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[HttpPost]
+ [Consumes("application/json")]
public ActionResult Authenticate([FromBody] LoginParamter login)
{
#if DEBUG
logger?.LogInformation("user \"{user}\" try to login with password \"{password}\"", login.Id, login.Password);
#endif
- var user = database.Users.FirstOrDefault(u => u.UserId == login.Id);
+ //var user = database.Users.SingleOrDefault(u => u.UserId == login.Id);
+ var user = QueryUserItemForAuthentication(login.Id);
if (user == null)
{
logger?.LogWarning("user \"{user}\" not found", login.Id);
@@ -63,8 +64,19 @@ namespace Blahblah.FlowerStory.Server.Controller
}
// record the session
- // TODO: singleton token, mobile
- var expires = 1200;
+ // TODO: singleton token
+ string clientApp;
+ int expires;
+ if (Request.Headers.TryGetValue("X-ClientAgent", out var clientAgent))
+ {
+ clientApp = "app";
+ expires = 30 * 24 * 60 * 60; // 30 days
+ }
+ else
+ {
+ clientApp = "browser";
+ expires = 20 * 60; // 20 mins
+ }
var now = DateTimeOffset.UtcNow;
var token = new TokenItem
{
@@ -74,13 +86,13 @@ namespace Blahblah.FlowerStory.Server.Controller
ActiveDateUnixTime = now.ToUnixTimeMilliseconds(),
ExpireDateUnixTime = now.AddSeconds(expires).ToUnixTimeMilliseconds(),
ExpireSeconds = expires,
- ClientApp = "browser", // TODO: support app later
- ClientAgent = Request.Headers.UserAgent
+ ClientApp = clientApp,
+ ClientAgent = clientAgent.Count > 0 ? clientAgent : Request.Headers.UserAgent,
+ DeviceId = Request.Headers.TryGetValue("X-DeviceId", out var deviceId) ? deviceId.ToString() : null
};
database.Tokens.Add(token);
user.ActiveDateUnixTime = token.ActiveDateUnixTime;
- database.Users.Update(user);
SaveDatabase();
Response.Headers.Add(AuthHeader, token.Id);
@@ -100,7 +112,7 @@ namespace Blahblah.FlowerStory.Server.Controller
/// 注销失败则返回错误内容
/// 注销成功
/// 认证失败
- [Route("logout")]
+ [Route("logout", Name = "logout")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[HttpPost]
@@ -142,17 +154,17 @@ namespace Blahblah.FlowerStory.Server.Controller
/// 成功注册则返回已注册的用户对象
/// 返回已注册的用户对象
/// 用户重复或其他服务器错误
- [Route("register")]
+ [Route("register", Name = "register")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
[HttpPost]
+ [Consumes("application/json")]
public ActionResult Register([FromBody] UserParameter user)
{
#if DEBUG
logger?.LogInformation("user register, {user}", user);
#endif
- var u = database.Users.FirstOrDefault(u => u.UserId == user.Id);
- if (u != null)
+ if (database.Users.Any(u => u.UserId == user.Id))
{
logger?.LogWarning("duplicate user \"{id}\"", user.Id);
return Problem("duplicateUser", "user/register", 500);
@@ -191,7 +203,7 @@ namespace Blahblah.FlowerStory.Server.Controller
/// 未找到登录会话或已过期
/// 用户已禁用
/// 未找到关联用户
- [Route("profile")]
+ [Route("profile", Name = "getProfile")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
@@ -236,12 +248,15 @@ namespace Blahblah.FlowerStory.Server.Controller
/// 未找到登录会话或已过期
/// 用户已禁用
/// 未找到关联用户
- [Route("update")]
+ /// 提交正文过大
+ [Route("update", Name = "updateProfile")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
+ [ProducesResponseType(StatusCodes.Status413PayloadTooLarge)]
[HttpPut]
+ [Consumes("application/json")]
public ActionResult Update([FromBody] UpdateParameter update)
{
#if DEBUG
@@ -257,23 +272,120 @@ namespace Blahblah.FlowerStory.Server.Controller
return NotFound();
}
- //var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
- //user.ActiveDateUnixTime = now;
user.Name = update.UserName;
user.Email = update.Email;
user.Mobile = update.Mobile;
- database.Users.Update(user);
SaveDatabase();
return Ok(user);
}
-//#if DEBUG
+ ///
+ /// 修改用户头像
+ ///
+ ///
+ /// 请求示例:
+ ///
+ /// PUT /api/user/update_avatar
+ /// Authorization: authorization id
+ ///
+ /// 参数:
+ ///
+ /// avatar: IFormFile?
+ ///
+ ///
+ /// 用户头像
+ /// 修改成功则返回 HTTP 204
+ /// 修改成功
+ /// 头像内容格式非法
+ /// 未找到登录会话或已过期
+ /// 用户已禁用
+ /// 未找到关联用户
+ /// 提交正文过大
+ [Route("update_avatar", Name = "updateAvatar")]
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
+ [ProducesResponseType(StatusCodes.Status400BadRequest)]
+ [ProducesResponseType(StatusCodes.Status401Unauthorized)]
+ [ProducesResponseType(StatusCodes.Status403Forbidden)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ [ProducesResponseType(StatusCodes.Status413PayloadTooLarge)]
+ [HttpPut]
+ [Consumes("multipart/form-data")]
+ [RequestSizeLimit(5 * 1024 * 1024)]
+ public ActionResult UploadAvatar(IFormFile? avatar)
+ {
+#if DEBUG
+ logger?.LogInformation("user update avatar, {avatar}", avatar);
+#endif
+ var (result, user) = CheckPermission();
+ if (result != null)
+ {
+ return result;
+ }
+ if (user == null)
+ {
+ return NotFound();
+ }
+
+ if (avatar?.Length > 0)
+ {
+ var file = WrapFormFile(avatar);
+ if (file == null)
+ {
+ return BadRequest();
+ }
+ user.Avatar = file.Content;
+ }
+ SaveDatabase();
+
+ return Ok(user);
+ }
+
+ ///
+ /// 移除用户头像
+ ///
+ ///
+ /// 请求示例:
+ ///
+ /// DELETE /api/user/remove_avatar
+ /// Authorization: authorization id
+ ///
+ ///
+ /// 移除成功则返回修改的数据库行数
+ /// 修改的数据库行数
+ /// 未找到登录会话或已过期
+ /// 用户已禁用
+ /// 未找到关联用户
+ [Route("remove_avatar", Name = "removeAvatar")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status401Unauthorized)]
+ [ProducesResponseType(StatusCodes.Status403Forbidden)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ [HttpDelete]
+ public ActionResult RemoveAvatar()
+ {
+ var (result, user) = CheckPermission();
+ if (result != null)
+ {
+ return result;
+ }
+ if (user == null)
+ {
+ return NotFound();
+ }
+
+ var count = RemoveUserAvatar(user.Id);
+ SaveDatabase();
+
+ return Ok(count);
+ }
+
+ //#if DEBUG
///
/// #DEBUG 获取所有用户
///
///
- [Route("debug_list")]
+ [Route("debug_list", Name = "debug_list")]
[HttpGet]
public ActionResult GetUsers()
{
@@ -284,12 +396,12 @@ namespace Blahblah.FlowerStory.Server.Controller
/// #DEBUG 获取所有 token
///
///
- [Route("debug_tokens")]
+ [Route("debug_tokens", Name = "debug_tokens")]
[HttpGet]
public ActionResult GetTokens()
{
return Ok(database.Tokens.ToArray());
}
-//#endif
+ //#endif
}
}
diff --git a/Server/Data/Model/FlowerItem.cs b/Server/Data/Model/FlowerItem.cs
index 2290e09..80d9e65 100644
--- a/Server/Data/Model/FlowerItem.cs
+++ b/Server/Data/Model/FlowerItem.cs
@@ -65,6 +65,12 @@ public class FlowerItem
[Column("photo")]
public byte[]? Photo { get; set; }
+ ///
+ /// 备注
+ ///
+ [Column("memo")]
+ public string? Memo { get; set; }
+
///
/// 购买时间
///
diff --git a/Server/Data/Model/RecordItem.cs b/Server/Data/Model/RecordItem.cs
index 65e556b..654f659 100644
--- a/Server/Data/Model/RecordItem.cs
+++ b/Server/Data/Model/RecordItem.cs
@@ -58,6 +58,12 @@ public class RecordItem
[Column("photo")]
public byte[]? Photo { get; set; }
+ ///
+ /// 备注
+ ///
+ [Column("memo")]
+ public string? Memo { get; set; }
+
///
/// 操作时间
///
diff --git a/Server/Data/Model/UserItem.cs b/Server/Data/Model/UserItem.cs
index 440a556..5377286 100644
--- a/Server/Data/Model/UserItem.cs
+++ b/Server/Data/Model/UserItem.cs
@@ -77,6 +77,13 @@ public class UserItem
[Column("mobile")]
public string? Mobile { get; set; }
+ ///
+ /// 用户头像
+ ///
+ [Column("avatar")]
+ [JsonIgnore]
+ public byte[]? Avatar { get; set; }
+
///
/// 注册时间
///
diff --git a/Server/Migrations/20230525004719_AddAvatarMemo.Designer.cs b/Server/Migrations/20230525004719_AddAvatarMemo.Designer.cs
new file mode 100644
index 0000000..9f36f2b
--- /dev/null
+++ b/Server/Migrations/20230525004719_AddAvatarMemo.Designer.cs
@@ -0,0 +1,214 @@
+//
+using System;
+using Blahblah.FlowerStory.Server.Data;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace Blahblah.FlowerStory.Server.Migrations
+{
+ [DbContext(typeof(FlowerDatabase))]
+ [Migration("20230525004719_AddAvatarMemo")]
+ partial class AddAvatarMemo
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "7.0.5");
+
+ modelBuilder.Entity("Blahblah.FlowerStory.Server.Data.Model.FlowerItem", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("fid");
+
+ b.Property("CategoryId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("categoryid");
+
+ b.Property("Cost")
+ .HasColumnType("real")
+ .HasColumnName("cost");
+
+ b.Property("DateBuyUnixTime")
+ .HasColumnType("INTEGER")
+ .HasColumnName("datebuy")
+ .HasAnnotation("Relational:JsonPropertyName", "dateBuy");
+
+ b.Property("Memo")
+ .HasColumnType("TEXT")
+ .HasColumnName("memo");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("name");
+
+ b.Property("OwnerId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("uid");
+
+ b.Property("Photo")
+ .HasColumnType("BLOB")
+ .HasColumnName("photo");
+
+ b.Property("Purchase")
+ .HasColumnType("TEXT")
+ .HasColumnName("purchase");
+
+ b.HasKey("Id");
+
+ b.ToTable("flowers");
+ });
+
+ modelBuilder.Entity("Blahblah.FlowerStory.Server.Data.Model.RecordItem", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("rid");
+
+ b.Property("ByUserId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("byuid");
+
+ b.Property("ByUserName")
+ .HasColumnType("TEXT")
+ .HasColumnName("byname");
+
+ b.Property("DateUnixTime")
+ .HasColumnType("INTEGER")
+ .HasColumnName("date")
+ .HasAnnotation("Relational:JsonPropertyName", "date");
+
+ b.Property("EventId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("eid");
+
+ b.Property("Memo")
+ .HasColumnType("TEXT")
+ .HasColumnName("memo");
+
+ b.Property("OwnerId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("uid");
+
+ b.Property("Photo")
+ .HasColumnType("BLOB")
+ .HasColumnName("photo");
+
+ b.HasKey("Id");
+
+ b.ToTable("records");
+ });
+
+ modelBuilder.Entity("Blahblah.FlowerStory.Server.Data.Model.TokenItem", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("TEXT")
+ .HasColumnName("tid");
+
+ b.Property("ActiveDateUnixTime")
+ .HasColumnType("INTEGER")
+ .HasColumnName("activedate")
+ .HasAnnotation("Relational:JsonPropertyName", "activeDate");
+
+ b.Property("ClientAgent")
+ .HasColumnType("TEXT")
+ .HasColumnName("clientagent");
+
+ b.Property("ClientApp")
+ .HasColumnType("TEXT")
+ .HasColumnName("clientapp");
+
+ b.Property("DeviceId")
+ .HasColumnType("TEXT")
+ .HasColumnName("deviceid");
+
+ b.Property("ExpireDateUnixTime")
+ .HasColumnType("INTEGER")
+ .HasColumnName("expiredate")
+ .HasAnnotation("Relational:JsonPropertyName", "expireDate");
+
+ b.Property("ExpireSeconds")
+ .HasColumnType("INTEGER")
+ .HasColumnName("expiresecs");
+
+ b.Property("LogonDateUnixTime")
+ .HasColumnType("INTEGER")
+ .HasColumnName("logondate")
+ .HasAnnotation("Relational:JsonPropertyName", "logonDate");
+
+ b.Property("UserId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("uid");
+
+ b.Property("VerifyCode")
+ .HasColumnType("TEXT")
+ .HasColumnName("verifycode");
+
+ b.HasKey("Id");
+
+ b.ToTable("tokens");
+ });
+
+ modelBuilder.Entity("Blahblah.FlowerStory.Server.Data.Model.UserItem", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("uid");
+
+ b.Property("ActiveDateUnixTime")
+ .HasColumnType("INTEGER")
+ .HasColumnName("activedate");
+
+ b.Property("Avatar")
+ .HasColumnType("BLOB")
+ .HasColumnName("avatar");
+
+ b.Property("Email")
+ .HasColumnType("TEXT")
+ .HasColumnName("email");
+
+ b.Property("Level")
+ .HasColumnType("INTEGER")
+ .HasColumnName("level");
+
+ b.Property("Mobile")
+ .HasColumnType("TEXT")
+ .HasColumnName("mobile");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("name");
+
+ b.Property("Password")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("password");
+
+ b.Property("RegisterDateUnixTime")
+ .HasColumnType("INTEGER")
+ .HasColumnName("regdate")
+ .HasAnnotation("Relational:JsonPropertyName", "registerDate");
+
+ b.Property("UserId")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("id");
+
+ b.HasKey("Id");
+
+ b.ToTable("users");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/Server/Migrations/20230525004719_AddAvatarMemo.cs b/Server/Migrations/20230525004719_AddAvatarMemo.cs
new file mode 100644
index 0000000..ab51165
--- /dev/null
+++ b/Server/Migrations/20230525004719_AddAvatarMemo.cs
@@ -0,0 +1,48 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace Blahblah.FlowerStory.Server.Migrations
+{
+ ///
+ public partial class AddAvatarMemo : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.AddColumn(
+ name: "avatar",
+ table: "users",
+ type: "BLOB",
+ nullable: true);
+
+ migrationBuilder.AddColumn(
+ name: "memo",
+ table: "records",
+ type: "TEXT",
+ nullable: true);
+
+ migrationBuilder.AddColumn(
+ name: "memo",
+ table: "flowers",
+ type: "TEXT",
+ nullable: true);
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropColumn(
+ name: "avatar",
+ table: "users");
+
+ migrationBuilder.DropColumn(
+ name: "memo",
+ table: "records");
+
+ migrationBuilder.DropColumn(
+ name: "memo",
+ table: "flowers");
+ }
+ }
+}
diff --git a/Server/Migrations/FlowerDatabaseModelSnapshot.cs b/Server/Migrations/FlowerDatabaseModelSnapshot.cs
index beaed6f..a736f1b 100644
--- a/Server/Migrations/FlowerDatabaseModelSnapshot.cs
+++ b/Server/Migrations/FlowerDatabaseModelSnapshot.cs
@@ -37,6 +37,10 @@ namespace Blahblah.FlowerStory.Server.Migrations
.HasColumnName("datebuy")
.HasAnnotation("Relational:JsonPropertyName", "dateBuy");
+ b.Property("Memo")
+ .HasColumnType("TEXT")
+ .HasColumnName("memo");
+
b.Property("Name")
.IsRequired()
.HasColumnType("TEXT")
@@ -83,6 +87,10 @@ namespace Blahblah.FlowerStory.Server.Migrations
.HasColumnType("INTEGER")
.HasColumnName("eid");
+ b.Property("Memo")
+ .HasColumnType("TEXT")
+ .HasColumnName("memo");
+
b.Property("OwnerId")
.HasColumnType("INTEGER")
.HasColumnName("uid");
@@ -157,6 +165,10 @@ namespace Blahblah.FlowerStory.Server.Migrations
.HasColumnType("INTEGER")
.HasColumnName("activedate");
+ b.Property("Avatar")
+ .HasColumnType("BLOB")
+ .HasColumnName("avatar");
+
b.Property("Email")
.HasColumnType("TEXT")
.HasColumnName("email");
diff --git a/Server/Program.cs b/Server/Program.cs
index 371f02c..8e02d63 100644
--- a/Server/Program.cs
+++ b/Server/Program.cs
@@ -25,7 +25,7 @@ public class Program
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
- //options.OperationFilter();
+ options.OperationFilter();
var scheme = new OpenApiSecurityScheme
{
@@ -53,7 +53,7 @@ public class Program
Description = "¼¼ĵݼеĵεΡ
API ĵ
"
});
- options.IncludeXmlComments(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Server.xml"));
+ options.IncludeXmlComments(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Server.xml"), true);
});
builder.Services.AddDbContext(options => options.UseSqlite("DataSource=flower.db;Cache=Shared"));
@@ -84,23 +84,26 @@ public class SwaggerHttpHeaderOperation : IOperationFilter
///
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
- var required = context.ApiDescription.RelativePath switch
+ switch (context.ApiDescription.RelativePath)
{
- "user/update" or
- "user/profile" or
- "user/logout" => true,
- _ => false
- };
- if (required)
- {
- operation.Parameters.Add(new OpenApiParameter
- {
- Name = "Authorization",
- Description = "Ȩ Token",
- In = ParameterLocation.Header,
- Required = true,
- Schema = new OpenApiSchema { Type = "string" }
- });
+ case "api/user/auth":
+ operation.Parameters.Add(new OpenApiParameter
+ {
+ Name = "X-ClientAgent",
+ Description = "ͻ˴",
+ In = ParameterLocation.Header,
+ Required = false,
+ Schema = new OpenApiSchema { Type = "string" }
+ });
+ operation.Parameters.Add(new OpenApiParameter
+ {
+ Name = "X-DeviceId",
+ Description = "豸 id",
+ In = ParameterLocation.Header,
+ Required = false,
+ Schema = new OpenApiSchema { Type = "string" }
+ });
+ break;
}
}
}
\ No newline at end of file