pull/1121/head
梁宇奇 2025-06-11 00:26:25 +08:00
parent 0f82d6e749
commit 00176bb2f8
21 changed files with 585 additions and 83 deletions

View File

@ -97,7 +97,7 @@ public class SysLoginController {
@ApiOperation(value = "发送登录短信验证码")
@PostMapping("/sendSmsVerificationCode")
public R sendSmsVerificationCode(@Validated @RequestBody LoginSmsReqVO vo) {
loginService.sendSmsVerificationCode(vo.getPhoneNumber(), vo.getCode(), vo.getUuid());
loginService.sendSmsVerificationCode(vo.getPhoneNumber(), true, vo.getCode(), vo.getUuid());
return R.ok();
}
@ -229,6 +229,8 @@ public class SysLoginController {
vo.setCurrentMenuIds(currentMenuIds);
vo.setCurrentMenuTreeNodes(BeanUtil.copyToList(menuService.getMenuTree(currentMenus),
MenuTreeNodeVO.class));
//当前档口
vo.setCurrentStoreId(roleInfoVO.getRelStoreId());
}
}
return vo;

View File

@ -1,6 +1,7 @@
package com.ruoyi.web.controller.system;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.constant.Constants;
@ -20,14 +21,17 @@ import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
*
*
* @author ruoyi
*/
@Api(tags = "菜单信息")
@Api(tags = "系统菜单/档口菜单")
@RequiredArgsConstructor
@RestController
@RequestMapping("/rest/v1/sys/menu")
@ -37,7 +41,8 @@ public class SysMenuController extends XktBaseController {
final TokenService tokenService;
@ApiOperation(value = "菜单列表查询")
@PreAuthorize("@ss.hasAnyRoles('admin,general_admin')")
@ApiOperation(value = "菜单列表查询 - 管理员")
@PostMapping("/list")
public R<List<MenuListItemVO>> list(@Validated @RequestBody MenuQueryVO vo) {
MenuQuery query = BeanUtil.toBean(vo, MenuQuery.class);
@ -45,7 +50,8 @@ public class SysMenuController extends XktBaseController {
return R.ok(BeanUtil.copyToList(list, MenuListItemVO.class));
}
@ApiOperation(value = "菜单树查询")
@PreAuthorize("@ss.hasAnyRoles('admin,general_admin')")
@ApiOperation(value = "菜单树查询 - 管理员")
@PostMapping("/tree")
public R<List<MenuTreeNodeVO>> tree(@Validated @RequestBody MenuQueryVO vo) {
MenuQuery query = BeanUtil.toBean(vo, MenuQuery.class);
@ -53,6 +59,34 @@ public class SysMenuController extends XktBaseController {
return R.ok(BeanUtil.copyToList(tree, MenuTreeNodeVO.class));
}
@PreAuthorize("@ss.hasAnyRoles('store')")
@ApiOperation(value = "菜单列表查询 - 档口")
@PostMapping("/store/list")
public R<List<MenuListItemVO>> listByStore(@Validated @RequestBody MenuQueryVO vo) {
MenuQuery query = BeanUtil.toBean(vo, MenuQuery.class);
Set<Long> usableMenuIds = menuService.storeUsableMenuIds();
List<MenuListItem> list = menuService.listMenu(query)
.stream()
.filter(o->usableMenuIds.contains(o.getMenuId()))
.collect(Collectors.toList());
return R.ok(BeanUtil.copyToList(list, MenuListItemVO.class));
}
@PreAuthorize("@ss.hasAnyRoles('store')")
@ApiOperation(value = "菜单树查询 - 档口")
@PostMapping("/store/tree")
public R<List<MenuTreeNodeVO>> treeByStore(@Validated @RequestBody MenuQueryVO vo) {
MenuQuery query = BeanUtil.toBean(vo, MenuQuery.class);
Set<Long> usableMenuIds = menuService.storeUsableMenuIds();
if (CollUtil.isNotEmpty(query.getMenuIds())) {
query.setMenuIds(new ArrayList<>(CollUtil.intersection(usableMenuIds, query.getMenuIds())));
} else {
query.setMenuIds(new ArrayList<>(usableMenuIds));
}
List<MenuTreeNode> tree = menuService.getMenuTree(query);
return R.ok(BeanUtil.copyToList(tree, MenuTreeNodeVO.class));
}
@ApiOperation(value = "菜单详情")
@GetMapping(value = "/{id}")
public R<MenuInfoVO> getInfo(@PathVariable("id") Long id) {

View File

@ -90,7 +90,7 @@ public class SysRegisterController extends BaseController {
@ApiOperation(value = "发送登录短信验证码")
@PostMapping("/sendSmsVerificationCode")
public R sendSmsVerificationCode(@Validated @RequestBody LoginSmsReqVO vo) {
loginService.sendSmsVerificationCode(vo.getPhoneNumber(), vo.getCode(), vo.getUuid());
loginService.sendSmsVerificationCode(vo.getPhoneNumber(), true, vo.getCode(), vo.getUuid());
return R.ok();
}

View File

@ -2,7 +2,9 @@ package com.ruoyi.web.controller.system;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.IdUtil;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.ruoyi.common.annotation.Log;
@ -15,6 +17,7 @@ import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.framework.web.service.TokenService;
import com.ruoyi.system.service.ISysMenuService;
import com.ruoyi.system.service.ISysRoleService;
import com.ruoyi.system.service.ISysUserService;
import com.ruoyi.web.controller.system.vo.*;
@ -30,6 +33,8 @@ import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
/**
@ -37,7 +42,7 @@ import java.util.stream.Collectors;
*
* @author ruoyi
*/
@Api(tags = "角色信息")
@Api(tags = "系统角色/档口子角色")
@RequiredArgsConstructor
@RestController
@RequestMapping("/rest/v1/sys/role")
@ -46,6 +51,7 @@ public class SysRoleController extends XktBaseController {
final ISysRoleService roleService;
final TokenService tokenService;
final ISysUserService userService;
final ISysMenuService sysMenuService;
@PreAuthorize("@ss.hasAnyRoles('admin,general_admin')")
@ApiOperation(value = "角色分页查询 - 管理员")
@ -65,22 +71,30 @@ public class SysRoleController extends XktBaseController {
return R.ok(BeanUtil.copyToList(all, RoleListItemVO.class));
}
@PreAuthorize("@ss.hasAnyRoles('admin,general_admin,store')")
@PreAuthorize("@ss.hasAnyRoles('store')")
@ApiOperation(value = "角色分页查询 - 档口")
@PostMapping("/store/page")
public R<PageVO<RoleListItemVO>> pageByStore(@Validated @RequestBody RoleQueryVO vo) {
RoleQuery query = BeanUtil.toBean(vo, RoleQuery.class);
Long storeId = SecurityUtils.getStoreId();
if (storeId == null) {
return R.ok(PageVO.empty(vo));
}
// 只能查询当前档口
query.setStoreIds(Collections.singletonList(SecurityUtils.getStoreId()));
query.setStoreIds(Collections.singletonList(storeId));
Page<UserListItem> page = PageHelper.startPage(vo.getPageNum(), vo.getPageSize());
roleService.listRole(query);
return R.ok(PageVO.of(page, RoleListItemVO.class));
}
@PreAuthorize("@ss.hasAnyRoles('admin,general_admin,store')")
@PreAuthorize("@ss.hasAnyRoles('store')")
@ApiOperation(value = "所有角色 - 档口")
@PostMapping("/store/all")
public R<List<RoleListItemVO>> allByStore() {
Long storeId = SecurityUtils.getStoreId();
if (storeId == null) {
return R.ok(ListUtil.empty());
}
RoleQuery query = new RoleQuery();
// 只能查询当前档口
query.setStoreIds(Collections.singletonList(SecurityUtils.getStoreId()));
@ -89,7 +103,7 @@ public class SysRoleController extends XktBaseController {
}
@PreAuthorize("@ss.hasAnyRoles('admin,general_admin,store')")
@ApiOperation(value = "角色详情")
@ApiOperation(value = "角色详情 - 管理员/档口")
@GetMapping(value = "/{id}")
public R<RoleInfoVO> getInfo(@PathVariable("id") Long id) {
RoleInfo infoDTO = roleService.getRoleById(id);
@ -99,21 +113,38 @@ public class SysRoleController extends XktBaseController {
return R.ok(vo);
}
@PreAuthorize("@ss.hasAnyRoles('admin,general_admin,store')")
@PreAuthorize("@ss.hasAnyRoles('admin,general_admin')")
@Log(title = "角色管理", businessType = BusinessType.INSERT)
@ApiOperation("创建角色")
@ApiOperation("创建角色 - 管理员")
@PostMapping("create")
public R<Long> create(@Valid @RequestBody RoleInfoEditVO vo) {
//TODO USER 如果不是超管根据当前档口设置storeId
RoleInfoEdit dto = BeanUtil.toBean(vo, RoleInfoEdit.class);
dto.setRoleId(null);
Long roleId = roleService.createRole(dto);
return R.ok(roleId);
}
@PreAuthorize("@ss.hasAnyRoles('admin,general_admin,store')")
@PreAuthorize("@ss.hasAnyRoles('store')")
@Log(title = "角色管理", businessType = BusinessType.INSERT)
@ApiOperation("创建角色 - 档口")
@PostMapping("/store/create")
public R<Long> createByStore(@Valid @RequestBody RoleInfoEditByStoreVO vo) {
Assert.notNull(SecurityUtils.getStoreId());
RoleInfoEdit dto = BeanUtil.toBean(vo, RoleInfoEdit.class);
dto.setRoleId(null);
dto.setStoreId(SecurityUtils.getStoreId());
//档口的roleKey使用uuid
dto.setRoleKey(IdUtil.fastSimpleUUID());
Set<Long> usableMenuIds = sysMenuService.storeUsableMenuIds();
CollUtil.emptyIfNull(dto.getMenuIds())
.forEach(menuId -> Assert.isTrue(usableMenuIds.contains(menuId), "菜单不可用"));
Long roleId = roleService.createRole(dto);
return R.ok(roleId);
}
@PreAuthorize("@ss.hasAnyRoles('admin,general_admin')")
@Log(title = "角色管理", businessType = BusinessType.UPDATE)
@ApiOperation("修改角色")
@ApiOperation("修改角色 - 管理员")
@PostMapping("edit")
public R<Long> edit(@Valid @RequestBody RoleInfoEditVO vo) {
Assert.notNull(vo.getRoleId(), "角色ID不能为空");
@ -124,9 +155,30 @@ public class SysRoleController extends XktBaseController {
return R.ok(vo.getRoleId());
}
@PreAuthorize("@ss.hasAnyRoles('store')")
@Log(title = "角色管理", businessType = BusinessType.UPDATE)
@ApiOperation("修改角色 - 档口")
@PostMapping("/store/edit")
public R<Long> editByStore(@Valid @RequestBody RoleInfoEditByStoreVO vo) {
Assert.notNull(SecurityUtils.getStoreId());
Assert.notNull(vo.getRoleId(), "角色ID不能为空");
RoleInfo info = roleService.getRoleById(vo.getRoleId());
Assert.isTrue(Objects.equals(info.getStoreId(), SecurityUtils.getStoreId()), "档口ID不匹配");
RoleInfoEdit dto = BeanUtil.toBean(vo, RoleInfoEdit.class);
//档口的roleKey不变
dto.setRoleKey(info.getRoleKey());
Set<Long> usableMenuIds = sysMenuService.storeUsableMenuIds();
CollUtil.emptyIfNull(dto.getMenuIds())
.forEach(menuId -> Assert.isTrue(usableMenuIds.contains(menuId), "菜单不可用"));
InfluenceScope scope = roleService.updateRole(dto);
// 清除用户缓存(退出登录)
tokenService.deleteCacheUser(scope.getUserIds());
return R.ok(vo.getRoleId());
}
@PreAuthorize("@ss.hasAnyRoles('admin,general_admin')")
@Log(title = "角色管理", businessType = BusinessType.EXPORT)
@ApiOperation("导出")
@ApiOperation("导出 - 管理员")
@PostMapping("/export")
public void export(@Validated @RequestBody RoleQueryVO vo, HttpServletResponse response) {
RoleQuery query = BeanUtil.toBean(vo, RoleQuery.class);
@ -135,9 +187,9 @@ public class SysRoleController extends XktBaseController {
util.exportExcel(response, list, "角色数据");
}
@PreAuthorize("@ss.hasAnyRoles('admin,general_admin,store')")
@PreAuthorize("@ss.hasAnyRoles('admin,general_admin')")
@Log(title = "角色管理", businessType = BusinessType.DELETE)
@ApiOperation("删除角色")
@ApiOperation("删除角色 - 管理员")
@PostMapping("/remove")
public R<Integer> remove(@Validated @RequestBody IdsVO vo) {
InfluenceScope scope = roleService.batchDelete(vo.getIds());
@ -146,9 +198,25 @@ public class SysRoleController extends XktBaseController {
return R.ok(scope.getCount());
}
@PreAuthorize("@ss.hasAnyRoles('admin,general_admin,store')")
// @PreAuthorize("@ss.hasAnyRoles('store')")
// @Log(title = "角色管理", businessType = BusinessType.DELETE)
// @ApiOperation("删除角色 - 档口")
// @PostMapping("/store/remove")
public R<Integer> removeByStore(@Validated @RequestBody IdsVO vo) {
Long storeId = SecurityUtils.getStoreId();
Assert.notNull(storeId);
List<RoleListItem> roles = roleService.listRole(RoleQuery.builder()
.storeIds(Collections.singletonList(storeId)).build());
roles.forEach(r -> Assert.isTrue(Objects.equals(r.getStoreId(), storeId), "档口ID不匹配"));
InfluenceScope scope = roleService.batchDelete(vo.getIds());
// 清除用户缓存(退出登录)
tokenService.deleteCacheUser(scope.getUserIds());
return R.ok(scope.getCount());
}
@PreAuthorize("@ss.hasAnyRoles('admin,general_admin')")
@Log(title = "角色管理", businessType = BusinessType.UPDATE)
@ApiOperation("修改角色状态")
@ApiOperation("修改角色状态 - 管理员")
@PostMapping("/changeStatus")
public R<Integer> changeStatus(@Validated @RequestBody BatchOptStatusVO vo) {
InfluenceScope scope = roleService.batchUpdateStatus(vo.getIds(), vo.getStatus());
@ -159,4 +227,22 @@ public class SysRoleController extends XktBaseController {
return R.ok(scope.getCount());
}
@PreAuthorize("@ss.hasAnyRoles('store')")
@Log(title = "角色管理", businessType = BusinessType.UPDATE)
@ApiOperation("修改角色状态 - 档口")
@PostMapping("/store/changeStatus")
public R<Integer> changeStatusByStore(@Validated @RequestBody BatchOptStatusVO vo) {
Long storeId = SecurityUtils.getStoreId();
Assert.notNull(storeId);
List<RoleListItem> roles = roleService.listRole(RoleQuery.builder()
.storeIds(Collections.singletonList(storeId)).build());
roles.forEach(r -> Assert.isTrue(Objects.equals(r.getStoreId(), storeId), "档口ID不匹配"));
InfluenceScope scope = roleService.batchUpdateStatus(vo.getIds(), vo.getStatus());
if (!Constants.SYS_NORMAL_STATUS.equals(vo.getStatus())) {
// 清除用户缓存(退出登录)
tokenService.deleteCacheUser(scope.getUserIds());
}
return R.ok(scope.getCount());
}
}

View File

@ -11,18 +11,19 @@ import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.UserInfo;
import com.ruoyi.common.core.domain.model.UserInfoEdit;
import com.ruoyi.common.core.domain.model.UserListItem;
import com.ruoyi.common.core.domain.model.UserQuery;
import com.ruoyi.common.core.domain.model.*;
import com.ruoyi.common.core.page.PageVO;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.framework.web.service.SysLoginService;
import com.ruoyi.framework.web.service.TokenService;
import com.ruoyi.system.service.ISysRoleService;
import com.ruoyi.system.service.ISysUserService;
import com.ruoyi.web.controller.system.vo.*;
import com.ruoyi.web.controller.xkt.vo.IdsVO;
import com.ruoyi.web.controller.xkt.vo.PhoneNumberVO;
import com.ruoyi.web.controller.xkt.vo.UsernameVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
@ -33,8 +34,7 @@ import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.util.Collections;
import java.util.List;
import java.util.*;
import java.util.stream.Collectors;
/**
@ -42,7 +42,7 @@ import java.util.stream.Collectors;
*
* @author ruoyi
*/
@Api(tags = "用户信息")
@Api(tags = "系统用户/档口子用户")
@RestController
@RequestMapping("/rest/v1/sys/user")
public class SysUserController extends BaseController {
@ -50,7 +50,11 @@ public class SysUserController extends BaseController {
@Autowired
private ISysUserService userService;
@Autowired
private ISysRoleService roleService;
@Autowired
private TokenService tokenService;
@Autowired
private SysLoginService loginService;
@PreAuthorize("@ss.hasAnyRoles('admin,general_admin')")
@ApiOperation(value = "用户分页查询 - 管理员")
@ -66,16 +70,18 @@ public class SysUserController extends BaseController {
@ApiOperation(value = "用户分页查询 - 档口")
@PostMapping("/store/page")
public R<PageVO<UserListItemVO>> pageByStore(@Validated @RequestBody UserQueryVO vo) {
Long storeId = SecurityUtils.getStoreId();
Assert.notNull(storeId);
UserQuery query = BeanUtil.toBean(vo, UserQuery.class);
// 只能查询当前档口
query.setStoreIds(Collections.singletonList(SecurityUtils.getStoreId()));
query.setStoreIds(Collections.singletonList(storeId));
Page<UserListItem> page = PageHelper.startPage(vo.getPageNum(), vo.getPageSize());
userService.listUser(query);
return R.ok(PageVO.of(page, UserListItemVO.class));
}
@PreAuthorize("@ss.hasAnyRoles('admin,general_admin,store')")
@ApiOperation(value = "用户详情")
@PreAuthorize("@ss.hasAnyRoles('admin,general_admin')")
@ApiOperation(value = "用户详情 - 管理员")
@GetMapping(value = "/{id}")
public R<UserInfoVO> getInfo(@PathVariable("id") Long id) {
UserInfo infoDTO = userService.getUserById(id);
@ -85,9 +91,33 @@ public class SysUserController extends BaseController {
return R.ok(vo);
}
@PreAuthorize("@ss.hasAnyRoles('store')")
@ApiOperation(value = "用户详情 - 档口")
@GetMapping(value = "/store/{id}")
public R<UserInfoVO> getInfoByStore(@PathVariable("id") Long id) {
Long storeId = SecurityUtils.getStoreId();
Assert.notNull(storeId);
UserInfo infoDTO = userService.getUserById(id);
boolean access = CollUtil.emptyIfNull(infoDTO.getRoles())
.stream()
.anyMatch(o -> Objects.equals(o.getStoreId(), storeId));
if (!access) {
return R.ok();
}
Set<Long> subRoleIds = roleService.getSubRoleIdsByStore(storeId);
UserInfoVO vo = BeanUtil.toBean(infoDTO, UserInfoVO.class);
// 只展示当前档口角色
vo.setRoles(CollUtil.emptyIfNull(vo.getRoles())
.stream()
.filter(r -> subRoleIds.contains(r.getRoleId()))
.collect(Collectors.toList()));
vo.setRoleIds(vo.getRoles().stream().map(RoleInfoVO::getRoleId).collect(Collectors.toList()));
return R.ok(vo);
}
@PreAuthorize("@ss.hasAnyRoles('admin,general_admin')")
@Log(title = "用户管理", businessType = BusinessType.INSERT)
@ApiOperation("创建用户")
@ApiOperation("创建用户 - 管理员")
@PostMapping("create")
public R<Long> create(@Valid @RequestBody UserInfoEditVO vo) {
UserInfoEdit dto = BeanUtil.toBean(vo, UserInfoEdit.class);
@ -96,9 +126,43 @@ public class SysUserController extends BaseController {
return R.ok(userId);
}
@PreAuthorize("@ss.hasAnyRoles('store')")
@ApiOperation(value = "发送子账号创建短信验证码 - 档口")
@PostMapping("/store/sendSmsVerificationCode")
public R sendSmsVerificationCode(@Validated @RequestBody PhoneNumberVO vo) {
loginService.sendSmsVerificationCode(vo.getPhoneNumber(), false, null, null);
return R.ok();
}
@PreAuthorize("@ss.hasAnyRoles('store')")
@Log(title = "用户管理", businessType = BusinessType.INSERT)
@ApiOperation("创建用户 - 档口")
@PostMapping("/store/create")
public R<Long> createByStore(@Valid @RequestBody UserInfoEditByStoreVO vo) {
Long storeId = SecurityUtils.getStoreId();
Assert.notNull(storeId);
Assert.notEmpty(vo.getPhonenumber(), "手机号不能为空");
Assert.notEmpty(vo.getUserName(), "账号名称不能为空");
//短信验证码
loginService.validateSmsVerificationCode(vo.getPhonenumber(), vo.getCode());
UserInfoEdit dto = BeanUtil.toBean(vo, UserInfoEdit.class);
dto.setUserId(null);
//昵称默认手机号
dto.setNickName(dto.getPhonenumber());
Set<Long> subRoleIds = roleService.getSubRoleIdsByStore(storeId);
if (CollUtil.isEmpty(dto.getRoleIds())) {
dto.setRoleIds(Collections.singletonList(ESystemRole.SELLER.getId()));
} else {
dto.getRoleIds().forEach(roleId -> Assert.isTrue(subRoleIds.contains(roleId), "角色非法"));
dto.getRoleIds().add(ESystemRole.SELLER.getId());
}
Long userId = userService.createUser(dto);
return R.ok(userId);
}
@PreAuthorize("@ss.hasAnyRoles('admin,general_admin')")
@Log(title = "用户管理", businessType = BusinessType.UPDATE)
@ApiOperation("修改用户")
@ApiOperation("修改用户 - 管理员")
@PostMapping("edit")
public R<Long> edit(@Valid @RequestBody UserInfoEditVO vo) {
Assert.notNull(vo.getUserId(), "用户ID不能为空");
@ -109,9 +173,42 @@ public class SysUserController extends BaseController {
return R.ok(userId);
}
@PreAuthorize("@ss.hasAnyRoles('store')")
@Log(title = "用户管理", businessType = BusinessType.UPDATE)
@ApiOperation("修改用户 - 档口")
@PostMapping("/store/edit")
public R<Long> editByStore(@Valid @RequestBody UserInfoEditByStoreVO vo) {
Long storeId = SecurityUtils.getStoreId();
Assert.notNull(storeId);
Assert.notEmpty(vo.getPhonenumber(), "用户手机号不能为空");
Set<Long> subRoleIds = roleService.getSubRoleIdsByStore(storeId);
UserInfo info = userService.getUserByPhoneNumber(vo.getPhonenumber());
Assert.notNull(info, "用户不存在");
List<Long> roleIds = new ArrayList<>();
List<Long> csRoleIds = new ArrayList<>();
for (RoleInfo roleInfo : CollUtil.emptyIfNull(info.getRoles())) {
if (subRoleIds.contains(roleInfo.getRoleId())) {
csRoleIds.add(roleInfo.getRoleId());
} else {
roleIds.add(roleInfo.getRoleId());
}
}
if (csRoleIds.isEmpty()) {
//原来不是当前档口子账号,校验短信验证码
loginService.validateSmsVerificationCode(vo.getPhonenumber(), vo.getCode());
}
UserInfoEdit dto = BeanUtil.toBean(info, UserInfoEdit.class);
roleIds.addAll(CollUtil.emptyIfNull(vo.getRoleIds()));
dto.setRoleIds(roleIds);
Long userId = userService.updateUser(dto);
// 清除用户缓存(退出登录)
tokenService.deleteCacheUser(userId);
return R.ok(userId);
}
@PreAuthorize("@ss.hasAnyRoles('admin,general_admin')")
@Log(title = "用户管理", businessType = BusinessType.EXPORT)
@ApiOperation("导出")
@ApiOperation("导出 - 管理员")
@PostMapping("/export")
public void export(@Validated @RequestBody UserQueryVO vo, HttpServletResponse response) {
UserQuery query = BeanUtil.toBean(vo, UserQuery.class);
@ -122,7 +219,7 @@ public class SysUserController extends BaseController {
@PreAuthorize("@ss.hasAnyRoles('admin,general_admin')")
@Log(title = "用户管理", businessType = BusinessType.IMPORT)
@ApiOperation("导入")
@ApiOperation("导入 - 管理员")
@PostMapping("/importData")
public AjaxResult importData(MultipartFile file, boolean updateSupport) throws Exception {
ExcelUtil<SysUser> util = new ExcelUtil<SysUser>(SysUser.class);
@ -133,7 +230,7 @@ public class SysUserController extends BaseController {
}
@PreAuthorize("@ss.hasAnyRoles('admin,general_admin')")
@ApiOperation("导入模板")
@ApiOperation("导入模板 - 管理员")
@PostMapping("/importTemplate")
public void importTemplate(HttpServletResponse response) {
ExcelUtil<SysUser> util = new ExcelUtil<SysUser>(SysUser.class);
@ -143,7 +240,7 @@ public class SysUserController extends BaseController {
@PreAuthorize("@ss.hasAnyRoles('admin,general_admin')")
@Log(title = "用户管理", businessType = BusinessType.DELETE)
@ApiOperation("删除用户")
@ApiOperation("删除用户 - 管理员")
@PostMapping("/remove")
public R<Integer> remove(@Validated @RequestBody IdsVO vo) {
int count = userService.batchDeleteUser(vo.getIds());
@ -154,7 +251,7 @@ public class SysUserController extends BaseController {
@PreAuthorize("@ss.hasAnyRoles('admin,general_admin')")
@Log(title = "用户管理", businessType = BusinessType.UPDATE)
@ApiOperation("修改用户状态")
@ApiOperation("修改用户状态 - 管理员")
@PostMapping("/changeStatus")
public R<Integer> changeStatus(@Validated @RequestBody BatchOptStatusVO vo) {
int count = userService.batchUpdateUserStatus(vo.getIds(), vo.getStatus());
@ -165,15 +262,55 @@ public class SysUserController extends BaseController {
return R.ok(count);
}
@PreAuthorize("@ss.hasAnyRoles('store')")
@Log(title = "用户管理", businessType = BusinessType.UPDATE)
@ApiOperation("修改用户状态 - 档口")
@PostMapping("/store/changeStatus")
public R<Integer> changeStatusByStore(@Validated @RequestBody BatchOptStatusVO vo) {
Long storeId = SecurityUtils.getStoreId();
Assert.notNull(storeId);
Assert.isTrue(vo.getIds().size() == 1, "档口不支持同时修改多个用户的状态");
UserInfo info = userService.getUserById(vo.getIds().get(0));
Set<Long> subRoleIds = roleService.getSubRoleIdsByStore(storeId);
boolean accessOpt = CollUtil.emptyIfNull(info.getRoles())
.stream()
.anyMatch(roleInfo -> subRoleIds.contains(roleInfo.getRoleId()));
Assert.isTrue(accessOpt, "当前角色无权修改用户状态");
int count = userService.batchUpdateUserStatus(vo.getIds(), vo.getStatus());
if (!Constants.SYS_NORMAL_STATUS.equals(vo.getStatus())) {
// 清除用户缓存(退出登录)
tokenService.deleteCacheUser(vo.getIds());
}
return R.ok(count);
}
/**
*
*/
@PreAuthorize("@ss.hasAnyRoles('admin,general_admin')")
@ApiOperation("重置密码")
@ApiOperation("重置密码 - 管理员")
@Log(title = "用户管理", businessType = BusinessType.UPDATE)
@PostMapping("/resetPwd")
public R resetPwd(@Validated @RequestBody PwdResetVO vo) {
userService.resetPassword(vo.getId(), vo.getNewPwd());
return R.ok();
}
@ApiOperation(value = "手机号是否已注册")
@PostMapping("/isPhoneNumberRegistered")
public R<Boolean> isPhoneNumberRegistered(@Validated @RequestBody PhoneNumberVO phoneNumberVO) {
SysUser u = new SysUser();
u.setPhonenumber(phoneNumberVO.getPhoneNumber());
boolean unique = userService.checkPhoneUnique(u);
return R.ok(!unique);
}
@ApiOperation(value = "账号名称是否已注册")
@PostMapping("/isUsernameRegistered")
public R<Boolean> isUsernameRegistered(@Validated @RequestBody UsernameVO usernameVO) {
SysUser u = new SysUser();
u.setUserName(usernameVO.getUserName());
boolean unique = userService.checkUserNameUnique(u);
return R.ok(!unique);
}
}

View File

@ -0,0 +1,45 @@
package com.ruoyi.web.controller.system.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.List;
/**
* @author liangyq
* @date 2025-05-29 16:39
*/
@ApiModel
@Data
public class RoleInfoEditByStoreVO {
/**
* ID
*/
@ApiModelProperty("角色ID")
private Long roleId;
/**
*
*/
@NotBlank(message = "角色名称不能为空")
@Size(min = 0, max = 30, message = "角色名称长度不能超过30个字")
@ApiModelProperty("角色名称")
private String roleName;
/**
*
*/
@NotNull(message = "显示顺序不能为空")
@ApiModelProperty("角色排序")
private Integer roleSort;
/**
*
*/
@ApiModelProperty("菜单ID集")
private List<Long> menuIds;
}

View File

@ -1,6 +1,6 @@
package com.ruoyi.web.controller.system.vo;
import com.ruoyi.web.controller.xkt.vo.BasePageVO;
import com.ruoyi.common.core.domain.vo.BasePageVO;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

View File

@ -0,0 +1,50 @@
package com.ruoyi.web.controller.system.vo;
import com.ruoyi.common.xss.Xss;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import java.util.List;
/**
* @author liangyq
* @date 2025-05-27
*/
@ApiModel
@Data
public class UserInfoEditByStoreVO {
/**
*
*/
@Xss(message = "用户账号不能包含脚本字符")
@Size(min = 0, max = 30, message = "用户账号长度不能超过30个字符")
@ApiModelProperty("用户账号")
private String userName;
/**
*
*/
@Size(min = 0, max = 11, message = "手机号码长度不能超过11个字符")
@ApiModelProperty("手机号码")
private String phonenumber;
@ApiModelProperty("短信验证码")
private String code;
/**
*
*/
@ApiModelProperty("密码")
private String password;
/**
* ID
*/
@ApiModelProperty("角色ID集")
private List<Long> roleIds;
}

View File

@ -25,4 +25,7 @@ public class UserLoginInfoVO extends UserInfoVO {
@ApiModelProperty("当前菜单树")
private List<MenuTreeNodeVO> currentMenuTreeNodes;
@ApiModelProperty("当前档口ID")
private Long currentStoreId;
}

View File

@ -1,3 +1,4 @@
/*
package com.ruoyi.web.controller.xkt;
import cn.hutool.core.bean.BeanUtil;
@ -20,12 +21,14 @@ import org.springframework.web.bind.annotation.*;
import java.util.List;
*/
/**
* Controller
*
* @author ruoyi
* @date 2025-03-26
*/
*//*
@Api(tags = "档口子账号")
@RestController
@RequiredArgsConstructor
@ -34,9 +37,11 @@ public class StoreRoleAccountController extends XktBaseController {
final IStoreRoleAccountService storeRoleAccService;
/**
*/
/**
*
*/
*//*
// @PreAuthorize("@ss.hasPermi('system:account:add')")
@ApiOperation(value = "新增档口子账号", httpMethod = "POST", response = R.class)
@Log(title = "新增档口子账号", businessType = BusinessType.INSERT)
@ -45,9 +50,11 @@ public class StoreRoleAccountController extends XktBaseController {
return R.ok(storeRoleAccService.insert(BeanUtil.toBean(roleAccVO, StoreRoleAccDTO.class)));
}
/**
*/
/**
*
*/
*//*
// @PreAuthorize("@ss.hasPermi('system:account:edit')")
@ApiOperation(value = "修改档口子账号", httpMethod = "PUT", response = R.class)
@Log(title = "修改档口子账号", businessType = BusinessType.UPDATE)
@ -56,9 +63,11 @@ public class StoreRoleAccountController extends XktBaseController {
return R.ok(storeRoleAccService.update(BeanUtil.toBean(accUpdateVO, StoreRoleAccUpdateDTO.class)));
}
/**
*/
/**
*
*/
*//*
// @PreAuthorize("@ss.hasPermi('system:role:list')")
@ApiOperation(value = "获取档口子账号列表", httpMethod = "POST", response = R.class)
@PostMapping("/list")
@ -66,9 +75,11 @@ public class StoreRoleAccountController extends XktBaseController {
return R.ok(BeanUtil.copyToList(storeRoleAccService.list(BeanUtil.toBean(accListVO, StoreRoleAccListDTO.class)), StoreRoleAccResVO.class));
}
/**
*/
/**
*
*/
*//*
// @PreAuthorize("@ss.hasPermi('system:account:query')")
@ApiOperation(value = "获取档口子账号详情", httpMethod = "POST", response = R.class)
@GetMapping(value = "/{storeRoleAccId}")
@ -76,9 +87,11 @@ public class StoreRoleAccountController extends XktBaseController {
return R.ok(BeanUtil.toBean(storeRoleAccService.selectByStoreRoleAccId(storeRoleAccId), StoreRoleAccDetailResVO.class));
}
/**
*/
/**
* /
*/
*//*
// @PreAuthorize("@ss.hasPermi('system:role:edit')")
@ApiOperation(value = "停用/启用档口子账号", httpMethod = "PUT", response = R.class)
@Log(title = "停用/启用档口子账号", businessType = BusinessType.UPDATE)
@ -88,3 +101,4 @@ public class StoreRoleAccountController extends XktBaseController {
}
}
*/

View File

@ -1,3 +1,4 @@
/*
package com.ruoyi.web.controller.xkt;
import cn.hutool.core.bean.BeanUtil;
@ -22,12 +23,14 @@ import org.springframework.web.bind.annotation.*;
import java.util.List;
*/
/**
* Controller
*
* @author ruoyi
* @date 2025-03-26
*/
*//*
@Api(tags = "档口子角色")
@RequiredArgsConstructor
@RestController
@ -41,9 +44,11 @@ public class StoreRoleController extends XktBaseController {
// TODO 还要返回档口角色所有的菜单
// TODO 还要返回档口角色所有的菜单
/**
*/
/**
*
*/
*//*
// @PreAuthorize("@ss.hasPermi('system:role:add')")
@ApiOperation(value = "新增档口子角色", httpMethod = "POST", response = R.class)
@Log(title = "新增档口子角色", businessType = BusinessType.INSERT)
@ -53,9 +58,11 @@ public class StoreRoleController extends XktBaseController {
}
/**
*/
/**
*
*/
*//*
// @PreAuthorize("@ss.hasPermi('system:role:edit')")
@ApiOperation(value = "编辑档口子角色", httpMethod = "PUT", response = R.class)
@Log(title = "编辑档口子角色", businessType = BusinessType.UPDATE)
@ -64,9 +71,11 @@ public class StoreRoleController extends XktBaseController {
return R.ok(storeRoleService.update(BeanUtil.toBean(storeRoleVO, StoreRoleDTO.class)));
}
/**
*/
/**
*
*/
*//*
// @PreAuthorize("@ss.hasPermi('system:role:query')")
@ApiOperation(value = "获取档口子角色详细信息", httpMethod = "GET", response = R.class)
@GetMapping(value = "/{storeRoleId}")
@ -80,9 +89,11 @@ public class StoreRoleController extends XktBaseController {
return R.ok(BeanUtil.toBean(storeRoleService.selectByStoreRoleId(storeRoleId), StoreRoleVO.class));
}
/**
*/
/**
*
*/
*//*
// @PreAuthorize("@ss.hasPermi('system:role:list')")
@ApiOperation(value = "查询档口销售出库列表", httpMethod = "POST", response = R.class)
@PostMapping("/list")
@ -90,9 +101,11 @@ public class StoreRoleController extends XktBaseController {
return R.ok(BeanUtil.copyToList(storeRoleService.list(BeanUtil.toBean(roleListVO, StoreRoleListDTO.class)), StoreRoleResVO.class));
}
/**
*/
/**
* /
*/
*//*
// @PreAuthorize("@ss.hasPermi('system:role:edit')")
@ApiOperation(value = "停用/启用档口子角色", httpMethod = "PUT", response = R.class)
@Log(title = "停用/启用档口子角色", businessType = BusinessType.UPDATE)
@ -103,3 +116,4 @@ public class StoreRoleController extends XktBaseController {
}
*/

View File

@ -1,3 +1,4 @@
/*
package com.ruoyi.web.controller.xkt;
import com.ruoyi.common.annotation.Log;
@ -15,21 +16,25 @@ import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
*/
/**
* Controller
*
* @author ruoyi
* @date 2025-03-26
*/
*//*
@RestController
@RequestMapping("/rest/v1/store-role-menus")
public class StoreRoleMenuController extends XktBaseController {
@Autowired
private IStoreRoleMenuService storeRoleMenuService;
/**
*/
/**
*
*/
*//*
// // @PreAuthorize("@ss.hasPermi('system:menu:list')")
@GetMapping("/list")
public TableDataInfo list(StoreRoleMenu storeRoleMenu) {
@ -38,9 +43,11 @@ public class StoreRoleMenuController extends XktBaseController {
return getDataTable(list);
}
/**
*/
/**
*
*/
*//*
// // @PreAuthorize("@ss.hasPermi('system:menu:export')")
@Log(title = "档口子角色菜单", businessType = BusinessType.EXPORT)
@PostMapping("/export")
@ -50,18 +57,22 @@ public class StoreRoleMenuController extends XktBaseController {
util.exportExcel(response, list, "档口子角色菜单数据");
}
/**
*/
/**
*
*/
*//*
// // @PreAuthorize("@ss.hasPermi('system:menu:query')")
@GetMapping(value = "/{storeRoleMenuId}")
public R getInfo(@PathVariable("storeRoleMenuId") Long storeRoleMenuId) {
return success(storeRoleMenuService.selectStoreRoleMenuByStoreRoleMenuId(storeRoleMenuId));
}
/**
*/
/**
*
*/
*//*
// // @PreAuthorize("@ss.hasPermi('system:menu:add')")
@Log(title = "档口子角色菜单", businessType = BusinessType.INSERT)
@PostMapping
@ -69,9 +80,11 @@ public class StoreRoleMenuController extends XktBaseController {
return success(storeRoleMenuService.insertStoreRoleMenu(storeRoleMenu));
}
/**
*/
/**
*
*/
*//*
// // @PreAuthorize("@ss.hasPermi('system:menu:edit')")
@Log(title = "档口子角色菜单", businessType = BusinessType.UPDATE)
@PutMapping
@ -79,9 +92,11 @@ public class StoreRoleMenuController extends XktBaseController {
return success(storeRoleMenuService.updateStoreRoleMenu(storeRoleMenu));
}
/**
*/
/**
*
*/
*//*
// // @PreAuthorize("@ss.hasPermi('system:menu:remove')")
@Log(title = "档口子角色菜单", businessType = BusinessType.DELETE)
@DeleteMapping("/{storeRoleMenuIds}")
@ -89,3 +104,4 @@ public class StoreRoleMenuController extends XktBaseController {
return success(storeRoleMenuService.deleteStoreRoleMenuByStoreRoleMenuIds(storeRoleMenuIds));
}
}
*/

View File

@ -0,0 +1,20 @@
package com.ruoyi.web.controller.xkt.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
/**
* @author liangyq
* @date 2025-06-06
*/
@ApiModel
@Data
public class UsernameVO {
@NotEmpty(message = "用户名不能为空")
@ApiModelProperty("用户名")
private String userName;
}

View File

@ -1,6 +1,9 @@
package com.ruoyi.common.core.domain.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
import java.util.List;
@ -10,6 +13,9 @@ import java.util.List;
* @date 2025-05-28 19:36
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class RoleQuery {
/**
* ID

View File

@ -1,6 +1,8 @@
package com.ruoyi.common.core.page;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.ListUtil;
import com.ruoyi.common.core.domain.vo.BasePageVO;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
@ -45,4 +47,8 @@ public class PageVO<T> implements Serializable {
BeanUtil.copyToList(page.getResult(), clazz));
}
public static <T, E extends BasePageVO> PageVO<T> empty(E params) {
return new PageVO((long) params.getPageNum(), (long) params.getPageSize(), 0L, ListUtil.empty());
}
}

View File

@ -240,17 +240,20 @@ public class SysLoginService {
/**
* /
*
* @param phoneNumber
* @param code code
* @param uuid uuid
* @param phoneNumber
* @param checkPicCode
* @param code code
* @param uuid uuid
*/
public void sendSmsVerificationCode(String phoneNumber, String code, String uuid) {
public void sendSmsVerificationCode(String phoneNumber, boolean checkPicCode, String code, String uuid) {
String k = CacheConstants.SMS_CAPTCHA_CODE_CD_PHONE_NUM_KEY + phoneNumber;
String v = redisCache.getCacheObject(k);
if (StrUtil.isNotEmpty(v)) {
throw new ServiceException("验证码发送间隔需大于60S");
}
validateCaptcha(null, code, uuid);
if (checkPicCode) {
validateCaptcha(null, code, uuid);
}
sendSmsVerificationCode(phoneNumber);
redisCache.setCacheObject(k, "1", 60, TimeUnit.SECONDS);
}

View File

@ -6,6 +6,7 @@ import com.ruoyi.system.domain.vo.menu.SysMenuDTO;
import java.util.Collection;
import java.util.List;
import java.util.Set;
/**
*
@ -104,4 +105,11 @@ public interface ISysMenuService {
* @return true false
*/
public boolean checkMenuExistRole(Long menuId);
/**
* ID
*
* @return
*/
Set<Long> storeUsableMenuIds();
}

View File

@ -3,6 +3,7 @@ package com.ruoyi.system.service;
import com.ruoyi.common.core.domain.model.*;
import java.util.List;
import java.util.Set;
/**
*
@ -81,4 +82,12 @@ public interface ISysRoleService {
* @return
*/
List<RoleSelectItem> listRoleSelectItem(Long userId);
/**
* ID
*
* @param storeId
* @return
*/
Set<Long> getSubRoleIdsByStore(Long storeId);
}

View File

@ -5,12 +5,14 @@ import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.domain.entity.SysMenu;
import com.ruoyi.common.core.domain.model.*;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.domain.SysRoleMenu;
import com.ruoyi.system.domain.vo.menu.SysMenuDTO;
import com.ruoyi.system.mapper.SysMenuMapper;
import com.ruoyi.system.mapper.SysRoleMapper;
@ -264,6 +266,12 @@ public class SysMenuServiceImpl implements ISysMenuService {
return result > 0;
}
@Override
public Set<Long> storeUsableMenuIds() {
//TODO USER
return roleMenuMapper.selectList(Wrappers.emptyWrapper()).stream().map(SysRoleMenu::getMenuId).collect(Collectors.toSet());
}
/**
*

View File

@ -5,6 +5,7 @@ import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.domain.entity.SysRole;
import com.ruoyi.common.core.domain.model.*;
@ -19,10 +20,8 @@ import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.*;
import java.util.stream.Collectors;
/**
*
@ -155,6 +154,17 @@ public class SysRoleServiceImpl implements ISysRoleService {
return userRoleMapper.listRoleSelectItem(userId);
}
@Override
public Set<Long> getSubRoleIdsByStore(Long storeId) {
if (storeId == null) {
return Collections.EMPTY_SET;
}
return roleMapper.selectList(Wrappers.lambdaQuery(SysRole.class).eq(SysRole::getStoreId, storeId))
.stream()
.map(SysRole::getRoleId)
.collect(Collectors.toSet());
}
/**
*
*

View File

@ -89,6 +89,7 @@ public class SysUserServiceImpl implements ISysUserService {
@Transactional(rollbackFor = Exception.class)
@Override
public Long createUser(UserInfoEdit userEdit) {
checkRoles(userEdit.getRoleIds());
// 创建用户
SysUser user = BeanUtil.toBean(userEdit, SysUser.class);
if (StrUtil.isNotEmpty(userEdit.getPassword())) {
@ -107,6 +108,7 @@ public class SysUserServiceImpl implements ISysUserService {
@Transactional(rollbackFor = Exception.class)
@Override
public Long updateUser(UserInfoEdit userEdit) {
checkRoles(userEdit.getRoleIds());
// 修改用户信息
Assert.notNull(userEdit.getUserId());
SysUser user = userMapper.selectById(userEdit.getUserId());
@ -470,6 +472,35 @@ public class SysUserServiceImpl implements ISysUserService {
}
}
private void checkRoles(Collection<Long> roleIds) {
if (CollUtil.isEmpty(roleIds) || roleIds.size() == 1) {
return;
}
int sellerCount = 0;
int otherCount = 0;
int subCount = 0;
for (Long roleId : roleIds) {
if (ESystemRole.SELLER.getId().equals(roleId)) {
sellerCount++;
} else if (ESystemRole.isDefaultRole(roleId)) {
otherCount++;
} else {
SysRole r = roleMapper.selectById(roleId);
if (r.getStoreId() != null) {
subCount++;
} else {
otherCount++;
}
}
}
if (subCount > 0 && otherCount > 0) {
throw new ServiceException("用户不能同时有子角色与\"电商卖家\"以外的角色");
}
if ((sellerCount + otherCount) > 0) {
throw new ServiceException("用户只能有一个系统角色");
}
}
/**
*
*