complete controllers

This commit is contained in:
Tsanie Lily 2023-05-25 14:41:03 +08:00
parent 1400fcdeb4
commit 3b5bd291f9
13 changed files with 664 additions and 56 deletions

View File

@ -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;
/// <summary>
/// 基础服务抽象类
/// </summary>
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.";
/// <summary>
/// 支持的图片文件签名
/// </summary>
protected static readonly List<byte[]> 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 }
};
/// <summary>
/// 自定义认证头的关键字
/// </summary>
@ -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;
}
/// <summary>
/// 读取文件到 byte 数组
/// </summary>
/// <param name="file">来自请求的文件</param>
/// <returns></returns>
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;
}
}
/// <summary>
/// 文件结果
/// </summary>
public record FileResult
{
/// <summary>
/// 文件名
/// </summary>
public string? Filename { get; init; }
/// <summary>
/// 文件内容
/// </summary>
[Required]
public required byte[] Content { get; init; }
}

View File

@ -0,0 +1,75 @@
using Blahblah.FlowerStory.Server.Data.Model;
using Microsoft.EntityFrameworkCore;
namespace Blahblah.FlowerStory.Server.Controller;
partial class BaseController
{
/// <summary>
/// 根据 uid 获取用户对象
/// </summary>
/// <param name="uid">用户唯一 id</param>
/// <returns></returns>
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();
}
/// <summary>
/// 根据 id 获取登录使用的用户对象
/// </summary>
/// <param name="id">用户 id</param>
/// <returns></returns>
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();
}
/// <summary>
/// 获取用户头像数据
/// </summary>
/// <param name="uid">用户唯一 id</param>
/// <returns></returns>
protected byte[]? QueryUserAvatar(int uid)
{
return database.Database.SqlQuery<byte[]>($"SELECT \"avatar\" AS \"Value\" FROM \"users\" WHERE \"uid\" = {uid} LIMIT 1").SingleOrDefault();
}
/// <summary>
/// 移除用户头像
/// </summary>
/// <param name="uid">用户唯一 id</param>
/// <returns></returns>
protected int RemoveUserAvatar(int uid)
{
return database.Database.ExecuteSql($"UPDATE \"users\" SET \"avatar\" = NULL WHERE \"uid\" = {uid}");
}
/// <summary>
/// 移除用户名下的花草
/// </summary>
/// <param name="uid">用户唯一 id</param>
/// <param name="fid">花草唯一 id</param>
/// <returns></returns>
protected int RemoveUserFlower(int uid, int fid)
{
return database.Database.ExecuteSql($"DELETE FROM \"flowers\" WHERE \"uid\" = {uid} AND \"fid\" = {fid}");
}
/// <summary>
/// 批量移除用户名下的花草
/// </summary>
/// <param name="uid">用户唯一 id</param>
/// <param name="fids">花草唯一 id 的数组</param>
/// <returns></returns>
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})");
}
}

View File

@ -8,7 +8,6 @@ namespace Blahblah.FlowerStory.Server.Controller;
/// 事件相关服务
/// </summary>
[ApiController]
[Consumes("application/json")]
[Produces("application/json")]
[Route("api/event")]
public class EventController : BaseController
@ -46,7 +45,7 @@ public class EventController : BaseController
/// <response code="401">未找到登录会话或已过期</response>
/// <response code="403">用户已禁用</response>
/// <response code="404">未找到关联用户</response>
[Route("query")]
[Route("query", Name = "queryEvents")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]

View File

@ -10,7 +10,6 @@ namespace Blahblah.FlowerStory.Server.Controller;
/// 花草相关服务
/// </summary>
[ApiController]
[Consumes("application/json")]
[Produces("application/json")]
[Route("api/flower")]
public class FlowerController : BaseController
@ -50,7 +49,7 @@ public class FlowerController : BaseController
/// <response code="401">未找到登录会话或已过期</response>
/// <response code="403">用户已禁用</response>
/// <response code="404">未找到关联用户</response>
[Route("query")]
[Route("query", Name = "queryFlowers")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
@ -128,7 +127,7 @@ public class FlowerController : BaseController
/// <response code="401">未找到登录会话或已过期</response>
/// <response code="403">用户已禁用</response>
/// <response code="404">未找到关联用户</response>
[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
/// <response code="401">未找到登录会话或已过期</response>
/// <response code="403">用户已禁用</response>
/// <response code="404">未找到关联用户</response>
[Route("removeany")]
[Route("removeany", Name = "removeFlowers")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[HttpPost]
[Consumes("application/json")]
public ActionResult<int> 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
/// <response code="401">未找到登录会话或已过期</response>
/// <response code="403">用户已禁用</response>
/// <response code="404">未找到关联用户</response>
[Route("add")]
[Route("add", Name = "addFlower")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[HttpPost]
[Consumes("application/json")]
public ActionResult<FlowerItem> AddFlower([FromBody] FlowerParameter flower)
{
var (result, user) = CheckPermission();
@ -278,12 +278,13 @@ public class FlowerController : BaseController
/// <response code="401">未找到登录会话或已过期</response>
/// <response code="403">用户已禁用</response>
/// <response code="404">未找到关联用户或者未找到将修改的花草对象</response>
[Route("update")]
[Route("update", Name = "updateFlower")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[HttpPut]
[Consumes("application/json")]
public ActionResult<FlowerItem> 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);

View File

@ -0,0 +1,55 @@
using Blahblah.FlowerStory.Server.Data;
using Microsoft.AspNetCore.Mvc;
namespace Blahblah.FlowerStory.Server.Controller;
/// <summary>
/// 图片相关服务
/// </summary>
[Produces("image/png")]
[Route("photo")]
public class ImageController : BaseController
{
/// <inheritdoc/>
public ImageController(FlowerDatabase database, ILogger<BaseController>? logger = null) : base(database, logger)
{
}
/// <summary>
/// 请求用户头像
/// </summary>
/// <remarks>
/// 请求示例:
///
/// GET /photo/avatar
/// Authorization: authorization id
///
/// </remarks>
/// <returns>认证通过则显示用户头像</returns>
/// <response code="200">返回头像</response>
/// <response code="401">认证失败</response>
/// <response code="404">未找到头像</response>
[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");
}
}

View File

@ -9,7 +9,6 @@ namespace Blahblah.FlowerStory.Server.Controller
/// 用户会话相关服务
/// </summary>
[ApiController]
[Consumes("application/json")]
[Produces("application/json")]
[Route("api/user")]
public partial class UserController : BaseController
@ -37,17 +36,19 @@ namespace Blahblah.FlowerStory.Server.Controller
/// <response code="204">返回自定义认证头</response>
/// <response code="401">认证失败</response>
/// <response code="404">未找到用户</response>
[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
/// <returns>注销失败则返回错误内容</returns>
/// <response code="204">注销成功</response>
/// <response code="401">认证失败</response>
[Route("logout")]
[Route("logout", Name = "logout")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[HttpPost]
@ -142,17 +154,17 @@ namespace Blahblah.FlowerStory.Server.Controller
/// <returns>成功注册则返回已注册的用户对象</returns>
/// <response code="200">返回已注册的用户对象</response>
/// <response code="500">用户重复或其他服务器错误</response>
[Route("register")]
[Route("register", Name = "register")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
[HttpPost]
[Consumes("application/json")]
public ActionResult<UserItem> 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
/// <response code="401">未找到登录会话或已过期</response>
/// <response code="403">用户已禁用</response>
/// <response code="404">未找到关联用户</response>
[Route("profile")]
[Route("profile", Name = "getProfile")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
@ -236,12 +248,15 @@ namespace Blahblah.FlowerStory.Server.Controller
/// <response code="401">未找到登录会话或已过期</response>
/// <response code="403">用户已禁用</response>
/// <response code="404">未找到关联用户</response>
[Route("update")]
/// <response code="413">提交正文过大</response>
[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<UserItem> 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
/// <summary>
/// 修改用户头像
/// </summary>
/// <remarks>
/// 请求示例:
///
/// PUT /api/user/update_avatar
/// Authorization: authorization id
///
/// 参数:
///
/// avatar: IFormFile?
///
/// </remarks>
/// <param name="avatar">用户头像</param>
/// <returns>修改成功则返回 HTTP 204</returns>
/// <response code="204">修改成功</response>
/// <response code="400">头像内容格式非法</response>
/// <response code="401">未找到登录会话或已过期</response>
/// <response code="403">用户已禁用</response>
/// <response code="404">未找到关联用户</response>
/// <response code="413">提交正文过大</response>
[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<UserItem> 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);
}
/// <summary>
/// 移除用户头像
/// </summary>
/// <remarks>
/// 请求示例:
///
/// DELETE /api/user/remove_avatar
/// Authorization: authorization id
///
/// </remarks>
/// <returns>移除成功则返回修改的数据库行数</returns>
/// <response code="200">修改的数据库行数</response>
/// <response code="401">未找到登录会话或已过期</response>
/// <response code="403">用户已禁用</response>
/// <response code="404">未找到关联用户</response>
[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
/// <summary>
/// #DEBUG 获取所有用户
/// </summary>
/// <returns></returns>
[Route("debug_list")]
[Route("debug_list", Name = "debug_list")]
[HttpGet]
public ActionResult<UserItem[]> GetUsers()
{
@ -284,12 +396,12 @@ namespace Blahblah.FlowerStory.Server.Controller
/// #DEBUG 获取所有 token
/// </summary>
/// <returns></returns>
[Route("debug_tokens")]
[Route("debug_tokens", Name = "debug_tokens")]
[HttpGet]
public ActionResult<TokenItem[]> GetTokens()
{
return Ok(database.Tokens.ToArray());
}
//#endif
//#endif
}
}

View File

@ -65,6 +65,12 @@ public class FlowerItem
[Column("photo")]
public byte[]? Photo { get; set; }
/// <summary>
/// 备注
/// </summary>
[Column("memo")]
public string? Memo { get; set; }
/// <summary>
/// 购买时间
/// </summary>

View File

@ -58,6 +58,12 @@ public class RecordItem
[Column("photo")]
public byte[]? Photo { get; set; }
/// <summary>
/// 备注
/// </summary>
[Column("memo")]
public string? Memo { get; set; }
/// <summary>
/// 操作时间
/// </summary>

View File

@ -77,6 +77,13 @@ public class UserItem
[Column("mobile")]
public string? Mobile { get; set; }
/// <summary>
/// 用户头像
/// </summary>
[Column("avatar")]
[JsonIgnore]
public byte[]? Avatar { get; set; }
/// <summary>
/// 注册时间
/// </summary>

View File

@ -0,0 +1,214 @@
// <auto-generated />
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
{
/// <inheritdoc />
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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("fid");
b.Property<int>("CategoryId")
.HasColumnType("INTEGER")
.HasColumnName("categoryid");
b.Property<decimal?>("Cost")
.HasColumnType("real")
.HasColumnName("cost");
b.Property<long>("DateBuyUnixTime")
.HasColumnType("INTEGER")
.HasColumnName("datebuy")
.HasAnnotation("Relational:JsonPropertyName", "dateBuy");
b.Property<string>("Memo")
.HasColumnType("TEXT")
.HasColumnName("memo");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("name");
b.Property<int>("OwnerId")
.HasColumnType("INTEGER")
.HasColumnName("uid");
b.Property<byte[]>("Photo")
.HasColumnType("BLOB")
.HasColumnName("photo");
b.Property<string>("Purchase")
.HasColumnType("TEXT")
.HasColumnName("purchase");
b.HasKey("Id");
b.ToTable("flowers");
});
modelBuilder.Entity("Blahblah.FlowerStory.Server.Data.Model.RecordItem", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("rid");
b.Property<int?>("ByUserId")
.HasColumnType("INTEGER")
.HasColumnName("byuid");
b.Property<string>("ByUserName")
.HasColumnType("TEXT")
.HasColumnName("byname");
b.Property<long>("DateUnixTime")
.HasColumnType("INTEGER")
.HasColumnName("date")
.HasAnnotation("Relational:JsonPropertyName", "date");
b.Property<int>("EventId")
.HasColumnType("INTEGER")
.HasColumnName("eid");
b.Property<string>("Memo")
.HasColumnType("TEXT")
.HasColumnName("memo");
b.Property<int>("OwnerId")
.HasColumnType("INTEGER")
.HasColumnName("uid");
b.Property<byte[]>("Photo")
.HasColumnType("BLOB")
.HasColumnName("photo");
b.HasKey("Id");
b.ToTable("records");
});
modelBuilder.Entity("Blahblah.FlowerStory.Server.Data.Model.TokenItem", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT")
.HasColumnName("tid");
b.Property<long>("ActiveDateUnixTime")
.HasColumnType("INTEGER")
.HasColumnName("activedate")
.HasAnnotation("Relational:JsonPropertyName", "activeDate");
b.Property<string>("ClientAgent")
.HasColumnType("TEXT")
.HasColumnName("clientagent");
b.Property<string>("ClientApp")
.HasColumnType("TEXT")
.HasColumnName("clientapp");
b.Property<string>("DeviceId")
.HasColumnType("TEXT")
.HasColumnName("deviceid");
b.Property<long>("ExpireDateUnixTime")
.HasColumnType("INTEGER")
.HasColumnName("expiredate")
.HasAnnotation("Relational:JsonPropertyName", "expireDate");
b.Property<int>("ExpireSeconds")
.HasColumnType("INTEGER")
.HasColumnName("expiresecs");
b.Property<long>("LogonDateUnixTime")
.HasColumnType("INTEGER")
.HasColumnName("logondate")
.HasAnnotation("Relational:JsonPropertyName", "logonDate");
b.Property<int>("UserId")
.HasColumnType("INTEGER")
.HasColumnName("uid");
b.Property<string>("VerifyCode")
.HasColumnType("TEXT")
.HasColumnName("verifycode");
b.HasKey("Id");
b.ToTable("tokens");
});
modelBuilder.Entity("Blahblah.FlowerStory.Server.Data.Model.UserItem", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("uid");
b.Property<long?>("ActiveDateUnixTime")
.HasColumnType("INTEGER")
.HasColumnName("activedate");
b.Property<byte[]>("Avatar")
.HasColumnType("BLOB")
.HasColumnName("avatar");
b.Property<string>("Email")
.HasColumnType("TEXT")
.HasColumnName("email");
b.Property<int>("Level")
.HasColumnType("INTEGER")
.HasColumnName("level");
b.Property<string>("Mobile")
.HasColumnType("TEXT")
.HasColumnName("mobile");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("name");
b.Property<string>("Password")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("password");
b.Property<long>("RegisterDateUnixTime")
.HasColumnType("INTEGER")
.HasColumnName("regdate")
.HasAnnotation("Relational:JsonPropertyName", "registerDate");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("id");
b.HasKey("Id");
b.ToTable("users");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,48 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Blahblah.FlowerStory.Server.Migrations
{
/// <inheritdoc />
public partial class AddAvatarMemo : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<byte[]>(
name: "avatar",
table: "users",
type: "BLOB",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "memo",
table: "records",
type: "TEXT",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "memo",
table: "flowers",
type: "TEXT",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "avatar",
table: "users");
migrationBuilder.DropColumn(
name: "memo",
table: "records");
migrationBuilder.DropColumn(
name: "memo",
table: "flowers");
}
}
}

View File

@ -37,6 +37,10 @@ namespace Blahblah.FlowerStory.Server.Migrations
.HasColumnName("datebuy")
.HasAnnotation("Relational:JsonPropertyName", "dateBuy");
b.Property<string>("Memo")
.HasColumnType("TEXT")
.HasColumnName("memo");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")
@ -83,6 +87,10 @@ namespace Blahblah.FlowerStory.Server.Migrations
.HasColumnType("INTEGER")
.HasColumnName("eid");
b.Property<string>("Memo")
.HasColumnType("TEXT")
.HasColumnName("memo");
b.Property<int>("OwnerId")
.HasColumnType("INTEGER")
.HasColumnName("uid");
@ -157,6 +165,10 @@ namespace Blahblah.FlowerStory.Server.Migrations
.HasColumnType("INTEGER")
.HasColumnName("activedate");
b.Property<byte[]>("Avatar")
.HasColumnType("BLOB")
.HasColumnName("avatar");
b.Property<string>("Email")
.HasColumnType("TEXT")
.HasColumnName("email");

View File

@ -25,7 +25,7 @@ public class Program
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
//options.OperationFilter<SwaggerHttpHeaderOperation>();
options.OperationFilter<SwaggerHttpHeaderOperation>();
var scheme = new OpenApiSecurityScheme
{
@ -53,7 +53,7 @@ public class Program
Description = "<p>花事记录,贴心的帮您记录花园中的点点滴滴。</p><p><b>API 文档</b></p>"
});
options.IncludeXmlComments(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Server.xml"));
options.IncludeXmlComments(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Server.xml"), true);
});
builder.Services.AddDbContext<FlowerDatabase>(options => options.UseSqlite("DataSource=flower.db;Cache=Shared"));
@ -84,23 +84,26 @@ public class SwaggerHttpHeaderOperation : IOperationFilter
/// <inheritdoc/>
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
var required = context.ApiDescription.RelativePath switch
{
"user/update" or
"user/profile" or
"user/logout" => true,
_ => false
};
if (required)
switch (context.ApiDescription.RelativePath)
{
case "api/user/auth":
operation.Parameters.Add(new OpenApiParameter
{
Name = "Authorization",
Description = "ÊÚȨ Token",
Name = "X-ClientAgent",
Description = "¿Í»§¶Ë´úÀí",
In = ParameterLocation.Header,
Required = true,
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;
}
}
}